Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ package at.bitfire.synctools.mapping.tasks.builder

import android.content.Entity
import at.bitfire.ical4android.Task
import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate
import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp
import org.dmfs.tasks.contract.TaskContract.Tasks

class CompletedBuilder : DmfsTaskFieldBuilder {

override fun build(from: Task, to: Entity) {
// COMPLETED must always be a DATE-TIME
to.entityValues.put(Tasks.COMPLETED, from.completedAt?.date?.toEpochMilli())
to.entityValues.put(Tasks.COMPLETED, from.completedAt?.normalizedDate()?.toTimestamp())
to.entityValues.put(Tasks.COMPLETED_IS_ALLDAY, 0)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ object AndroidTimeUtils {
* Same as [toInstant], but returns a UNIX timestamp (in milliseconds) instead of an [Instant].
*/
fun Temporal.toTimestamp(): Long =
toInstant().epochSecond * 1000
toInstant().toEpochMilli()

/**
* Converts this [Temporal] to a [ZonedDateTime] that is created from the timestamp returned by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,24 @@ package at.bitfire.synctools.mapping.tasks.builder
import android.content.ContentValues
import android.content.Entity
import androidx.core.content.contentValuesOf
import at.bitfire.DefaultTimezoneRule
import at.bitfire.ical4android.Task
import at.bitfire.synctools.test.assertContentValuesEqual
import net.fortuna.ical4j.model.property.Completed
import org.dmfs.tasks.contract.TaskContract.Tasks
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.time.Instant
import java.time.LocalDateTime

@RunWith(RobolectricTestRunner::class)
class CompletedBuilderTest {

@get:Rule
val tzRule = DefaultTimezoneRule("Europe/Berlin")

private val builder = CompletedBuilder()

@Test
Expand Down Expand Up @@ -50,4 +56,22 @@ class CompletedBuilderTest {
), result.entityValues)
}

@Test
fun `COMPLETED is floating LocalDateTime`() {
// COMPLETED without timezone (floating) must not crash with ClassCastException
// A floating COMPLETED is represented as a string without 'Z' (e.g. "20240601T120000")
val result = Entity(ContentValues())
builder.build(
from = Task(completedAt = Completed("20240601T120000")),
to = result
)
// floating date-time is interpreted in system default timezone
val expectedTimestamp = LocalDateTime.of(2024, 6, 1, 12, 0, 0)
.atZone(tzRule.defaultZoneId).toInstant().toEpochMilli()
assertContentValuesEqual(contentValuesOf(
Comment thread
sunkup marked this conversation as resolved.
Tasks.COMPLETED to expectedTimestamp,
Tasks.COMPLETED_IS_ALLDAY to 0
), result.entityValues)
}

}
Loading