From ee82f683bd3303dcb27a0febc9346c692290c656 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 21 Jul 2023 01:02:20 -0500 Subject: [PATCH] Update microsoft converter Add due dates, creation dates, and modification dates --- .../sync/microsoft/MicrosoftConverter.kt | 55 +++++++++++++------ .../java/org/tasks/sync/microsoft/Tasks.kt | 5 +- .../microsoft/ConvertFromMicrosoftTests.kt | 32 ++++++++++- .../sync/microsoft/ConvertToMicrosoftTests.kt | 43 +++++++++++++++ .../microsoft/basic_task_with_due_date.txt | 27 +++++++++ 5 files changed, 142 insertions(+), 20 deletions(-) create mode 100644 app/src/test/resources/microsoft/basic_task_with_due_date.txt diff --git a/app/src/main/java/org/tasks/sync/microsoft/MicrosoftConverter.kt b/app/src/main/java/org/tasks/sync/microsoft/MicrosoftConverter.kt index 2e1fac274..2a45f6ef0 100644 --- a/app/src/main/java/org/tasks/sync/microsoft/MicrosoftConverter.kt +++ b/app/src/main/java/org/tasks/sync/microsoft/MicrosoftConverter.kt @@ -4,7 +4,9 @@ import com.todoroo.astrid.data.Task import org.tasks.data.CaldavTask import org.tasks.data.TagData import org.tasks.time.DateTime +import org.tasks.time.DateTimeUtils.startOfDay import java.text.SimpleDateFormat +import java.time.ZonedDateTime import java.util.Locale import java.util.TimeZone @@ -17,29 +19,19 @@ object MicrosoftConverter { defaultPriority: Int, ) { title = remote.title - notes = remote.body?.content?.takeIf { it.isNotBlank() } + notes = remote.body?.content?.takeIf { remote.body.contentType == "text" && it.isNotBlank() } priority = when { remote.importance == Tasks.Task.Importance.high -> Task.Priority.HIGH priority != Task.Priority.HIGH -> priority defaultPriority != Task.Priority.HIGH -> defaultPriority else -> Task.Priority.NONE } - completionDate = remote.completedDateTime - ?.let { - val tz = TimeZone.getTimeZone(it.timeZone) - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.ssssss", Locale.US) - .apply { timeZone = tz } - .parse(it.dateTime) - ?.time - ?.let { ts -> DateTime(ts, tz).toLocal().millis } - ?: System.currentTimeMillis() - } - ?: 0L + completionDate = remote.completedDateTime.toLong(System.currentTimeMillis()) + dueDate = remote.dueDateTime.toLong(0L) + creationDate = remote.createdDateTime.parseDateTime() + modificationDate = remote.lastModifiedDateTime.parseDateTime() // checklist to subtasks - // due date // repeat - // modification date - // creation date // sync reminders // sync files } @@ -65,15 +57,44 @@ object MicrosoftConverter { Tasks.Task.Status.notStarted }, categories = tags.map { it.name!! }.takeIf { it.isNotEmpty() }, + dueDateTime = if (hasDueDate()) { + Tasks.Task.DateTime( + dateTime = DateTime(dueDate.startOfDay()).toUTC().toString("yyyy-MM-dd'T'HH:mm:ss.SSS0000"), + timeZone = "UTC" + ) + } else { + null + }, + lastModifiedDateTime = DateTime(modificationDate).toUTC().toString("yyyy-MM-dd'T'HH:mm:ss.SSS0000'Z'"), + createdDateTime = DateTime(creationDate).toUTC().toString("yyyy-MM-dd'T'HH:mm:ss.SSS0000'Z'"), completedDateTime = if (isCompleted) { - Tasks.Task.CompletedDateTime( + Tasks.Task.DateTime( dateTime = DateTime(completionDate).toUTC() .toString("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS"), timeZone = "UTC", ) } else { null - } + }, +// isReminderOn = ) } + + private fun String?.parseDateTime(): Long = + this + ?.let { ZonedDateTime.parse(this).toInstant().toEpochMilli() } + ?: System.currentTimeMillis() + + private fun Tasks.Task.DateTime?.toLong(default: Long): Long = + this + ?.let { + val tz = TimeZone.getTimeZone(it.timeZone) + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.ssssss", Locale.US) + .apply { timeZone = tz } + .parse(it.dateTime) + ?.time + ?.let { ts -> DateTime(ts, tz).toLocal().millis } + ?: default + } + ?: 0L } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/sync/microsoft/Tasks.kt b/app/src/main/java/org/tasks/sync/microsoft/Tasks.kt index d0fa3b223..96c6b74e9 100644 --- a/app/src/main/java/org/tasks/sync/microsoft/Tasks.kt +++ b/app/src/main/java/org/tasks/sync/microsoft/Tasks.kt @@ -18,7 +18,8 @@ data class Tasks( val isReminderOn: Boolean = false, val createdDateTime: String? = null, val lastModifiedDateTime: String? = null, - val completedDateTime: CompletedDateTime? = null, + val completedDateTime: DateTime? = null, + val dueDateTime: DateTime? = null, val linkedResources: List? = null, @field:Json(name = "@removed") val removed: Removed? = null, ) { @@ -38,7 +39,7 @@ data class Tasks( val reason: String, ) - data class CompletedDateTime( + data class DateTime( val dateTime: String, val timeZone: String, ) diff --git a/app/src/test/java/org/tasks/sync/microsoft/ConvertFromMicrosoftTests.kt b/app/src/test/java/org/tasks/sync/microsoft/ConvertFromMicrosoftTests.kt index 09516df3b..78cbada85 100644 --- a/app/src/test/java/org/tasks/sync/microsoft/ConvertFromMicrosoftTests.kt +++ b/app/src/test/java/org/tasks/sync/microsoft/ConvertFromMicrosoftTests.kt @@ -66,4 +66,34 @@ class ConvertFromMicrosoftTests { assertEquals(DateTime(2022, 9, 18, 0, 0).millis, local.completionDate) } } -} \ No newline at end of file + + @Test + fun parseDueDate() { + val (local, _) = TestUtilities.mstodo("microsoft/basic_task_with_due_date.txt") + withTZ("America/Chicago") { + assertEquals(DateTime(2023, 7, 19, 0, 0).millis, local.dueDate) + } + } + + @Test + fun parseCreationDate() { + val (local, _) = TestUtilities.mstodo("microsoft/basic_task_with_due_date.txt") + withTZ("America/Chicago") { + assertEquals( + DateTime(2023, 7, 19, 23, 20, 56, 9).millis, + local.creationDate + ) + } + } + + @Test + fun parseModificationDate() { + val (local, _) = TestUtilities.mstodo("microsoft/basic_task_with_due_date.txt") + withTZ("America/Chicago") { + assertEquals( + DateTime(2023, 7, 19, 23, 21, 6, 269).millis, + local.modificationDate + ) + } + } +} diff --git a/app/src/test/java/org/tasks/sync/microsoft/ConvertToMicrosoftTests.kt b/app/src/test/java/org/tasks/sync/microsoft/ConvertToMicrosoftTests.kt index 2ff8c3e56..adea0d055 100644 --- a/app/src/test/java/org/tasks/sync/microsoft/ConvertToMicrosoftTests.kt +++ b/app/src/test/java/org/tasks/sync/microsoft/ConvertToMicrosoftTests.kt @@ -5,12 +5,14 @@ import com.todoroo.astrid.data.Task import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Test +import org.tasks.TestUtilities.withTZ import org.tasks.makers.CaldavTaskMaker.REMOTE_ID import org.tasks.makers.CaldavTaskMaker.newCaldavTask import org.tasks.makers.TagDataMaker.NAME import org.tasks.makers.TagDataMaker.newTagData import org.tasks.makers.TaskMaker.COMPLETION_TIME import org.tasks.makers.TaskMaker.DESCRIPTION +import org.tasks.makers.TaskMaker.DUE_TIME import org.tasks.makers.TaskMaker.PRIORITY import org.tasks.makers.TaskMaker.TITLE import org.tasks.makers.TaskMaker.newTask @@ -112,4 +114,45 @@ class ConvertToMicrosoftTests { ) assertEquals(listOf("tag1", "tag2"), remote.categories) } + + @Test + fun setCreationTime() { + withTZ("America/Chicago") { + val remote = Task( + creationDate = DateTime(2023, 7, 21, 0, 42, 13, 475).millis, + ).toRemote(newCaldavTask(), emptyList()) + assertEquals( + "2023-07-21T05:42:13.4750000Z", + remote.createdDateTime + ) + } + } + + @Test + fun setModificationTime() { + withTZ("America/Chicago") { + val remote = Task( + modificationDate = DateTime(2023, 7, 21, 0, 49, 4, 3).millis, + ).toRemote(newCaldavTask(), emptyList()) + assertEquals( + "2023-07-21T05:49:04.0030000Z", + remote.lastModifiedDateTime + ) + } + } + + @Test + fun setDueDateTime() { + withTZ("America/Chicago") { + val remote = newTask( + with( + DUE_TIME, + DateTime(2023, 7, 21, 13, 30) + ) + ) + .toRemote(newCaldavTask(), emptyList()) + assertEquals("2023-07-21T05:00:00.0000000", remote.dueDateTime?.dateTime) + assertEquals("UTC", remote.dueDateTime?.timeZone) + } + } } \ No newline at end of file diff --git a/app/src/test/resources/microsoft/basic_task_with_due_date.txt b/app/src/test/resources/microsoft/basic_task_with_due_date.txt new file mode 100644 index 000000000..8120c2fef --- /dev/null +++ b/app/src/test/resources/microsoft/basic_task_with_due_date.txt @@ -0,0 +1,27 @@ +{ + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(todoTask)", + "@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/todo/lists/AQMkADAwATNiZmYAZC04OABiMC0xZDlkLTAwAi0wMAoALgAAA8dKmrSa60tBjeiKoPukmoQBAEkTxp6Wx2BEtV1vFJWK7bIAAAIBEgAAAA==/tasks/delta?$deltatoken=l7WI41swwioT5csv4k99ngHvnDU_spnidrOYzb78--8L_CvTDIAkbXl6MuXT-elf4jUsC_1pCyBUISy7PuGdnyLUQmoalQs1lf0G56o9TRE.2lrmzz4U-RHVGMXDGcA8SE7GeYKapG_mWFDPJgqZsjo", + "value": [ + { + "@odata.type": "#microsoft.graph.todoTask", + "@odata.etag": "W/\"SRPGnpbHYES1XW8UlYrtsgAGXHw+qw==\"", + "importance": "high", + "isReminderOn": false, + "status": "notStarted", + "title": "Test", + "createdDateTime": "2023-07-20T04:20:56.0091837Z", + "lastModifiedDateTime": "2023-07-20T04:21:06.2690268Z", + "hasAttachments": false, + "categories": [], + "id": "AQMkADAwATNiZmYAZC04OABiMC0xZDlkLTAwAi0wMAoARgAAA8dKmrSa60tBjeiKoPukmoQHAEkTxp6Wx2BEtV1vFJWK7bIAAAIBEgAAAEkTxp6Wx2BEtV1vFJWK7bIABlxbFkEAAAA=", + "body": { + "content": "", + "contentType": "text" + }, + "dueDateTime": { + "dateTime": "2023-07-19T05:00:00.0000000", + "timeZone": "UTC" + } + } + ] +} \ No newline at end of file