Skip to content
Draft
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,6 +8,7 @@ package at.bitfire.vcard4android.contactrow

import android.content.ContentValues
import android.provider.ContactsContract.CommonDataKinds.Event
import androidx.annotation.VisibleForTesting
import at.bitfire.vcard4android.Contact
import at.bitfire.vcard4android.LabeledProperty
import at.bitfire.vcard4android.Utils.trimToNull
Expand All @@ -16,33 +17,67 @@ import ezvcard.property.Anniversary
import ezvcard.property.Birthday
import ezvcard.util.PartialDate
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException
import java.util.logging.Level
import java.time.temporal.Temporal

object EventHandler: DataRowHandler() {
object EventHandler : DataRowHandler() {

// source: https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Contacts/src/com/android/contacts/util/CommonDateUtils.java;drc=61197364367c9e404c7da6900658f1b16c42d0da;l=25
private val acceptableFormats: List<Pair<DateTimeFormatter, (String, DateTimeFormatter) -> Temporal>> = listOf(
// Formats provided by Android's CommonDateUtils
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") to OffsetDateTime::parse,
DateTimeFormatter.ofPattern("yyyy-MM-dd") to LocalDate::parse,
// Additional common formats
DateTimeFormatter.ISO_OFFSET_DATE_TIME to OffsetDateTime::parse, // "yyyy-MM-dd'T'HH:mm:ssXXX"
)

override fun forMimeType() = Event.CONTENT_ITEM_TYPE

/**
* Tries to parse a date string into a [Temporal] object using multiple acceptable formats.
* Returns the parsed [Temporal] if successful, or `null` if none of the formats match.
* @param dateString The date string to parse.
* @return If format is:
* - `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` or `yyyy-MM-dd'T'HH:mm:ssXXX` -> [OffsetDateTime]
* - `yyyy-MM-dd` -> [LocalDate]
* - else -> `null`
*/
@VisibleForTesting
internal fun parseStartDate(dateString: String): Temporal? {
for ((formatter, parse) in acceptableFormats) {
try {
return parse(dateString, formatter)
} catch (_: DateTimeParseException) {
// ignore: given date is not valid
continue
}
}

// could not parse date
return null
}

override fun handle(values: ContentValues, contact: Contact) {
super.handle(values, contact)

val dateStr = values.getAsString(Event.START_DATE) ?: return
var full: LocalDate? = null
var partial: PartialDate? = null
try {
full = LocalDate.parse(dateStr)
} catch(e: DateTimeParseException) {
try {
partial = PartialDate.parse(dateStr)
} catch (e: IllegalArgumentException) {
logger.log(Level.WARNING, "Couldn't parse birthday/anniversary date from database", e)
}
val full: Temporal? = parseStartDate(dateStr)
val partial: PartialDate? = if (full == null) try {
PartialDate.parse(dateStr)
} catch (_: IllegalArgumentException) {
null
} else {
null
}

if (full != null || partial != null)
when (values.getAsInteger(Event.TYPE)) {
Event.TYPE_ANNIVERSARY ->
contact.anniversary = if (full != null) Anniversary(full) else Anniversary(partial)
contact.anniversary =
if (full != null) Anniversary(full) else Anniversary(partial)

Event.TYPE_BIRTHDAY ->
contact.birthDay = if (full != null) Birthday(full) else Birthday(partial)
/* Event.TYPE_OTHER,
Expand Down
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides from empty, I think we should test at least

  • the four formats of the AOSP Contacts app (see issue),
  • possibly additional common date / date-time formats that we want to support.

Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,28 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.ZoneOffset

@RunWith(RobolectricTestRunner::class)
class EventHandlerTest {

@Test
fun test_parseStartDate_ISO_UTC_DateTime() {
assertEquals(
OffsetDateTime.of(1953, 10, 15, 23, 10, 0, 0, ZoneOffset.UTC),
EventHandler.parseStartDate("1953-10-15T23:10:00Z")
)
}

@Test
fun test_parseStartDate_ISO_Date() {
assertEquals(
LocalDate.of(1953, 10, 15),
EventHandler.parseStartDate("1953-10-15")
)
}

@Test
fun testStartDate_Empty() {
val contact = Contact()
Expand Down