Skip to content

Commit

Permalink
Unify date formats
Browse files Browse the repository at this point in the history
  • Loading branch information
fibelatti committed Oct 15, 2024
1 parent 73545a3 commit 39343da
Show file tree
Hide file tree
Showing 15 changed files with 55 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class PinboardEndToEndTests {
fun userCanLoginAndFetchBookmarks() {
// Arrange
PinboardMockServer.setResponses(
PinboardMockServer.updateResponse(updateTimestamp = dateFormatter.nowAsTzFormat()),
PinboardMockServer.updateResponse(updateTimestamp = dateFormatter.nowAsDataFormat()),
PinboardMockServer.allBookmarksResponse(isEmpty = false),
)

Expand Down Expand Up @@ -112,7 +112,7 @@ class PinboardEndToEndTests {
fun userCanLoginAndAddBookmarks() {
// Arrange
PinboardMockServer.setResponses(
PinboardMockServer.updateResponse(updateTimestamp = dateFormatter.nowAsTzFormat()),
PinboardMockServer.updateResponse(updateTimestamp = dateFormatter.nowAsDataFormat()),
PinboardMockServer.allBookmarksResponse(isEmpty = true),
PinboardMockServer.addBookmarkResponse(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,30 @@ class DateFormatter @Inject constructor(
private val userRepository: UserRepository,
) {

fun tzFormatToDisplayFormat(input: String): String = try {
val parsed: LocalDateTime = LocalDateTime.Format { byUnicodePattern(FORMAT_TZ) }.parse(input)
.toInstant(TimeZone.UTC)
.toLocalDateTime(TimeZone.currentSystemDefault())
getLocalDateTimeFormat(userRepository.preferredDateFormat).format(parsed)
} catch (_: Exception) {
input
/**
* Each server/resource uses a different format. Samples:
*
* - **Pinboard bookmark**: 2024-10-21T11:00:00Z
* - **Pinboard note**: 2024-10-21 11:00:00
* - **Linkding**: 2024-10-21T11:00:00.123456Z
*/
private val dateTimeFormat = LocalDateTime.Format {
byUnicodePattern(pattern = "yyyy-MM-dd['T'][' ']HH:mm:ss[.SSSSSS]['Z']")
}

fun notesFormatToDisplayFormat(input: String): String = try {
val parsed: LocalDateTime = LocalDateTime.Format { byUnicodePattern(FORMAT_NOTES) }.parse(input)
fun dataFormatToDisplayFormat(input: String): String = try {
val parsed: LocalDateTime = dateTimeFormat.parse(input)
.toInstant(TimeZone.UTC)
.toLocalDateTime(TimeZone.currentSystemDefault())
getLocalDateTimeFormat(userRepository.preferredDateFormat).format(parsed)
} catch (_: Exception) {
input
}

fun nowAsTzFormat(): String {
fun nowAsDataFormat(): String {
val nowInUtc = Clock.System.now().toLocalDateTime(TimeZone.UTC)

return LocalDateTime.Format { byUnicodePattern(FORMAT_TZ) }.format(nowInUtc)
return dateTimeFormat.format(nowInUtc).replace(" ", "")
}

fun displayFormatToMillis(input: String): Long {
Expand Down Expand Up @@ -86,17 +88,10 @@ class DateFormatter @Inject constructor(
}
}

char(value = ',')
char(value = ' ')
chars(", ")
hour(padding = Padding.ZERO)
char(value = ':')
minute(padding = Padding.ZERO)
}
}

private companion object {

const val FORMAT_TZ = "yyyy-MM-dd'T'HH:mm:ss[.SSSSSS]'Z'"
const val FORMAT_NOTES = "yyyy-MM-dd HH:mm:ss"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ class BookmarkLocalMapper @Inject constructor(
description = description,
id = id,
dateAdded = dateAdded,
displayDateAdded = dateFormatter.tzFormatToDisplayFormat(dateAdded),
displayDateAdded = dateFormatter.dataFormatToDisplayFormat(dateAdded),
dateModified = dateModified,
displayDateModified = dateFormatter.tzFormatToDisplayFormat(dateModified),
displayDateModified = dateFormatter.dataFormatToDisplayFormat(dateModified),
private = shared == false,
readLater = unread == true,
tags = tagNames?.ifBlank { null }?.split(" ")?.sorted()?.map(::Tag),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ class BookmarkRemoteMapper @Inject constructor(
description = description.orEmpty(),
id = requireNotNull(id?.toString()),
dateAdded = dateAdded,
displayDateAdded = dateFormatter.tzFormatToDisplayFormat(input = dateAdded),
displayDateAdded = dateFormatter.dataFormatToDisplayFormat(input = dateAdded),
dateModified = dateModified ?: dateAdded,
displayDateModified = dateFormatter.tzFormatToDisplayFormat(input = dateModified ?: dateAdded),
displayDateModified = dateFormatter.dataFormatToDisplayFormat(input = dateModified ?: dateAdded),
private = shared == false,
readLater = unread == true,
tags = tagNames?.sorted()?.map(::Tag),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ class PostsDataSourceLinkdingApi @Inject constructor(
*/
override suspend fun update(): Result<String> = resultFromNetwork {
linkdingApi.getBookmarks(limit = 1)
}.mapCatching { dateFormatter.nowAsTzFormat() }
}.mapCatching { dateFormatter.nowAsDataFormat() }

override suspend fun add(post: Post): Result<Post> {
val resolvedId = post.id.ifBlank { null }?.toIntOrNull()
val resolvedPost = post.copy(
dateAdded = post.dateAdded.ifNullOrBlank { dateFormatter.nowAsTzFormat() },
dateModified = dateFormatter.nowAsTzFormat(),
dateAdded = post.dateAdded.ifNullOrBlank { dateFormatter.nowAsDataFormat() },
dateModified = dateFormatter.nowAsDataFormat(),
)

return if (connectivityInfoProvider.isConnected()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package com.fibelatti.pinboard.features.notes.data.model
import com.fibelatti.core.functional.Mapper
import com.fibelatti.pinboard.core.util.DateFormatter
import com.fibelatti.pinboard.features.notes.domain.model.Note
import javax.inject.Inject
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import javax.inject.Inject

@Serializable
data class NoteDto(
Expand All @@ -24,8 +24,8 @@ class NoteDtoMapper @Inject constructor(
return Note(
id = param.id,
title = param.title.orEmpty(),
createdAt = param.createdAt?.let(dateFormatter::notesFormatToDisplayFormat).orEmpty(),
updatedAt = param.updatedAt?.let(dateFormatter::notesFormatToDisplayFormat).orEmpty(),
createdAt = param.createdAt?.let(dateFormatter::dataFormatToDisplayFormat).orEmpty(),
updatedAt = param.updatedAt?.let(dateFormatter::dataFormatToDisplayFormat).orEmpty(),
text = param.text.orEmpty(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ import com.fibelatti.pinboard.features.posts.domain.PostsRepository
import com.fibelatti.pinboard.features.posts.domain.model.Post
import com.fibelatti.pinboard.features.posts.domain.model.PostListResult
import com.fibelatti.pinboard.features.tags.domain.model.Tag
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

class PostsDataSourceNoApi @Inject constructor(
private val postsDao: PostsDao,
private val postDtoMapper: PostDtoMapper,
private val dateFormatter: DateFormatter,
) : PostsRepository {

override suspend fun update(): Result<String> = Success(dateFormatter.nowAsTzFormat())
override suspend fun update(): Result<String> = Success(dateFormatter.nowAsDataFormat())

override suspend fun add(post: Post): Result<Post> {
val existingPost = resultFrom {
Expand All @@ -42,7 +42,7 @@ class PostsDataSourceNoApi @Inject constructor(
description = post.title,
extended = post.description,
hash = existingPost?.hash ?: post.id.ifEmpty { UUID.randomUUID().toString() },
time = existingPost?.time ?: post.dateAdded.ifEmpty { dateFormatter.nowAsTzFormat() },
time = existingPost?.time ?: post.dateAdded.ifEmpty { dateFormatter.nowAsDataFormat() },
shared = if (post.private == true) AppConfig.PinboardApiLiterals.NO else AppConfig.PinboardApiLiterals.YES,
toread = if (post.readLater == true) AppConfig.PinboardApiLiterals.YES else AppConfig.PinboardApiLiterals.NO,
tags = post.tags?.joinToString(AppConfig.PinboardApiLiterals.TAG_SEPARATOR_RESPONSE) { it.name }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class PostsDataSourcePinboardApi @Inject constructor(
override suspend fun add(post: Post): Result<Post> {
val resolvedPost = post.copy(
id = post.id.ifNullOrBlank { UUID.randomUUID().toString() },
dateAdded = post.dateAdded.ifNullOrBlank { dateFormatter.nowAsTzFormat() },
dateAdded = post.dateAdded.ifNullOrBlank { dateFormatter.nowAsDataFormat() },
)

return if (connectivityInfoProvider.isConnected()) {
Expand Down Expand Up @@ -229,7 +229,7 @@ class PostsDataSourcePinboardApi @Inject constructor(
emit(localData(false))

val userLastUpdate = userRepository.lastUpdate.takeIf(String::isNotBlank)
val apiLastUpdate = update().getOrDefault(dateFormatter.nowAsTzFormat())
val apiLastUpdate = update().getOrDefault(dateFormatter.nowAsDataFormat())

if (userLastUpdate != null && userLastUpdate == apiLastUpdate && !forceRefresh) {
emit(localData(true))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import com.fibelatti.pinboard.core.util.DateFormatter
import com.fibelatti.pinboard.features.posts.domain.model.PendingSync
import com.fibelatti.pinboard.features.posts.domain.model.Post
import com.fibelatti.pinboard.features.tags.domain.model.Tag
import kotlinx.serialization.Serializable
import java.net.URLDecoder
import java.net.URLEncoder
import javax.inject.Inject
import kotlinx.serialization.Serializable

const val POST_TABLE_NAME = "Posts"

Expand Down Expand Up @@ -51,7 +51,7 @@ class PostDtoMapper @Inject constructor(
description = extended.orEmpty(),
id = hash,
dateAdded = time,
displayDateAdded = dateFormatter.tzFormatToDisplayFormat(time),
displayDateAdded = dateFormatter.dataFormatToDisplayFormat(time),
private = shared == PinboardApiLiterals.NO,
readLater = toread == PinboardApiLiterals.YES,
tags = if (tags.isBlank()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,48 +22,48 @@ internal class DateFormatterTest {
}

@Test
fun `WHEN tzFormatToDisplayFormat is called AND preferred date format is DayMonthYearWithTime THEN it is correctly returned`() {
fun `WHEN dataFormatToDisplayFormat is called AND preferred date format is DayMonthYearWithTime THEN it is correctly returned`() {
every { testUserRepository.preferredDateFormat } returns PreferredDateFormat.DayMonthYearWithTime

assertThat(dateFormatter.tzFormatToDisplayFormat("1991-08-20T11:00:00Z"))
assertThat(dateFormatter.dataFormatToDisplayFormat("1991-08-20T11:00:00Z"))
.isEqualTo("20/08/91, 11:00")
}

@Test
fun `WHEN tzFormatToDisplayFormat is called AND preferred date format is DayMonthYearWithTime THEN it is correctly returned - with millis`() {
fun `WHEN dataFormatToDisplayFormat is called AND preferred date format is DayMonthYearWithTime THEN it is correctly returned - with millis`() {
every { testUserRepository.preferredDateFormat } returns PreferredDateFormat.DayMonthYearWithTime

assertThat(dateFormatter.tzFormatToDisplayFormat("1991-08-20T11:00:00.123456Z"))
assertThat(dateFormatter.dataFormatToDisplayFormat("1991-08-20T11:00:00.123456Z"))
.isEqualTo("20/08/91, 11:00")
}

@Test
fun `WHEN tzFormatToDisplayFormat is called AND preferred date format is MonthDayYearWithTime THEN it is correctly returned`() {
fun `WHEN dataFormatToDisplayFormat is called AND preferred date format is MonthDayYearWithTime THEN it is correctly returned`() {
every { testUserRepository.preferredDateFormat } returns PreferredDateFormat.MonthDayYearWithTime

assertThat(dateFormatter.tzFormatToDisplayFormat("1991-08-20T11:00:00Z"))
assertThat(dateFormatter.dataFormatToDisplayFormat("1991-08-20T11:00:00Z"))
.isEqualTo("08/20/91, 11:00")
}

@Test
fun `WHEN notesFormatToDisplayFormat is called AND preferred date format is DayMonthYearWithTime THEN it is correctly returned`() {
fun `WHEN dataFormatToDisplayFormat is called AND preferred date format is DayMonthYearWithTime THEN it is correctly returned - notes`() {
every { testUserRepository.preferredDateFormat } returns PreferredDateFormat.DayMonthYearWithTime

assertThat(dateFormatter.notesFormatToDisplayFormat("1991-08-20 11:00:00"))
assertThat(dateFormatter.dataFormatToDisplayFormat("1991-08-20 11:00:00"))
.isEqualTo("20/08/91, 11:00")
}

@Test
fun `WHEN notesFormatToDisplayFormat is called AND preferred date format is MonthDayYearWithTime THEN it is correctly returned`() {
fun `WHEN dataFormatToDisplayFormat is called AND preferred date format is MonthDayYearWithTime THEN it is correctly returned - notes`() {
every { testUserRepository.preferredDateFormat } returns PreferredDateFormat.MonthDayYearWithTime

assertThat(dateFormatter.notesFormatToDisplayFormat("1991-08-20 11:00:00"))
assertThat(dateFormatter.dataFormatToDisplayFormat("1991-08-20 11:00:00"))
.isEqualTo("08/20/91, 11:00")
}

@Test
fun `WHEN nowAsTzFormat is called THEN format should be correct`() {
val result = dateFormatter.nowAsTzFormat().matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{6}Z$".toRegex())
fun `WHEN nowAsDataFormat is called THEN format should be correct`() {
val result = dateFormatter.nowAsDataFormat().matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{6}Z$".toRegex())

assertThat(result).isTrue()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class BookmarkLocalMapperTest {

private val mapper = BookmarkLocalMapper(
dateFormatter = mockk {
every { tzFormatToDisplayFormat(any()) } answers { firstArg() }
every { dataFormatToDisplayFormat(any()) } answers { firstArg() }
},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class BookmarkRemoteMapperTest {

private val mapper = BookmarkRemoteMapper(
dateFormatter = mockk {
every { tzFormatToDisplayFormat(any()) } answers { firstArg() }
every { dataFormatToDisplayFormat(any()) } answers { firstArg() }
},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ internal class NoteDtoMapperTest {
val mockTitle = "Some title"
val mockText = "Some text"

every { mockDateFormatter.notesFormatToDisplayFormat(inputDate) } returns outputDate
every { mockDateFormatter.dataFormatToDisplayFormat(inputDate) } returns outputDate

// WHEN
val result = mapper.map(
Expand All @@ -115,6 +115,6 @@ internal class NoteDtoMapperTest {
),
)

verify(exactly = 2) { mockDateFormatter.notesFormatToDisplayFormat(inputDate) }
verify(exactly = 2) { mockDateFormatter.dataFormatToDisplayFormat(inputDate) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ class PostsDataSourcePinboardApiTest {
every { UUID.randomUUID() } returns mockk {
every { this@mockk.toString() } returns mockHash
}
every { mockDateFormatter.nowAsTzFormat() } returns mockTime
every { mockDateFormatter.nowAsDataFormat() } returns mockTime
coEvery { mockApi.update() } returns UpdateDto(mockFutureTime)
coEvery {
mockApi.add(
Expand Down Expand Up @@ -523,7 +523,7 @@ class PostsDataSourcePinboardApiTest {
every { UUID.randomUUID() } returns mockk {
every { this@mockk.toString() } returns mockHash
}
every { mockDateFormatter.nowAsTzFormat() } returns mockTime
every { mockDateFormatter.nowAsDataFormat() } returns mockTime
every { mockPostDtoMapper.map(expectedPost) } returns createPost()

// WHEN
Expand Down Expand Up @@ -820,7 +820,7 @@ class PostsDataSourcePinboardApiTest {
fun `WHEN update fails THEN nowAsTzFormat is used instead`() = runTest {
// GIVEN
coEvery { mockApi.update() } throws Exception()
every { mockDateFormatter.nowAsTzFormat() } returns mockTime
every { mockDateFormatter.nowAsDataFormat() } returns mockTime

// WHEN
val result = dataSource.getAllPosts(
Expand All @@ -839,7 +839,7 @@ class PostsDataSourcePinboardApiTest {
// THEN
assertThat(result.toList().map { it.getOrThrow() })
.isEqualTo(listOf(mockLocalData, mockUpToDateLocalData))
verify { mockDateFormatter.nowAsTzFormat() }
verify { mockDateFormatter.nowAsDataFormat() }
coVerify(exactly = 0) { mockApi.getAllPosts() }
coVerify(exactly = 0) { mockDao.deleteAllSyncedPosts() }
coVerify(exactly = 0) { mockDao.savePosts(any()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class PostDtoMapperTest {

private val mapper = PostDtoMapper(
dateFormatter = mockk {
every { tzFormatToDisplayFormat(any()) } answers { invocation.args[0] as String }
every { dataFormatToDisplayFormat(any()) } answers { invocation.args[0] as String }
},
)

Expand Down

0 comments on commit 39343da

Please sign in to comment.