Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/bitfireAT/ical4android.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRicki Hirner <hirner@bitfire.at>2022-09-11 18:47:10 +0300
committerGitHub <noreply@github.com>2022-09-11 18:47:10 +0300
commit238a3f25511d00559672f5f9e94d2afad8c19a18 (patch)
treef706155bacf6c2465cff87e2005ce776789c445a
parent2c31b0e8860efaa7ba5ec53b244b48463b9cd694 (diff)
Introduce AndroidCompatTimeZoneRegistry (#60)
* androidify populated start times * introduce AndroidCompatTimeZoneRegistry (closes #57, #58, #59)
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/AndroidCompatTimeZoneRegistryTest.kt70
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/AndroidEventTest.kt16
-rw-r--r--src/main/java/at/bitfire/ical4android/AndroidCompatTimeZoneRegistry.kt83
-rw-r--r--src/main/java/at/bitfire/ical4android/AndroidEvent.kt1
-rw-r--r--src/main/java/at/bitfire/ical4android/util/AndroidTimeUtils.kt2
-rw-r--r--src/main/resources/ical4j.properties1
6 files changed, 173 insertions, 0 deletions
diff --git a/src/androidTest/java/at/bitfire/ical4android/AndroidCompatTimeZoneRegistryTest.kt b/src/androidTest/java/at/bitfire/ical4android/AndroidCompatTimeZoneRegistryTest.kt
new file mode 100644
index 0000000..5958e16
--- /dev/null
+++ b/src/androidTest/java/at/bitfire/ical4android/AndroidCompatTimeZoneRegistryTest.kt
@@ -0,0 +1,70 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
+package at.bitfire.ical4android
+
+import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory
+import net.fortuna.ical4j.model.TimeZoneRegistry
+import org.junit.Assert.*
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import java.time.ZoneId
+import java.time.zone.ZoneRulesException
+
+class AndroidCompatTimeZoneRegistryTest {
+
+ lateinit var ical4jRegistry: TimeZoneRegistry
+ lateinit var registry: TimeZoneRegistry
+
+ val systemKnowsKyiv =
+ try {
+ ZoneId.of("Europe/Kyiv")
+ true
+ } catch (e: ZoneRulesException) {
+ false
+ }
+
+ @Before
+ fun createRegistry() {
+ ical4jRegistry = DefaultTimeZoneRegistryFactory.getInstance().createRegistry()
+ registry = AndroidCompatTimeZoneRegistry.Factory().createRegistry()
+ }
+
+
+ @Test
+ fun getTimeZone_Existing() {
+ assertEquals(
+ ical4jRegistry.getTimeZone("Europe/Vienna"),
+ registry.getTimeZone("Europe/Vienna")
+ )
+ }
+
+ @Test
+ fun getTimeZone_Existing_Kiev() {
+ Assume.assumeFalse(systemKnowsKyiv)
+ val tz = registry.getTimeZone("Europe/Kiev")
+ assertFalse(tz === ical4jRegistry.getTimeZone("Europe/Kiev")) // we have made a copy
+ assertEquals("Europe/Kiev", tz?.id)
+ assertEquals("Europe/Kiev", tz?.vTimeZone?.timeZoneId?.value)
+ }
+
+ @Test
+ fun getTimeZone_Existing_Kyiv() {
+ Assume.assumeFalse(systemKnowsKyiv)
+
+ /* Unfortunately, AndroidCompatTimeZoneRegistry can't rewrite to Europy/Kyiv to anything because
+ it doesn't know a valid Android name for it. */
+ assertEquals(
+ ical4jRegistry.getTimeZone("Europe/Kyiv"),
+ registry.getTimeZone("Europe/Kyiv")
+ )
+ }
+
+ @Test
+ fun getTimeZone_NotExisting() {
+ assertNull(registry.getTimeZone("Test/NotExisting"))
+ }
+
+} \ No newline at end of file
diff --git a/src/androidTest/java/at/bitfire/ical4android/AndroidEventTest.kt b/src/androidTest/java/at/bitfire/ical4android/AndroidEventTest.kt
index 556fa0b..cb094ca 100644
--- a/src/androidTest/java/at/bitfire/ical4android/AndroidEventTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/AndroidEventTest.kt
@@ -29,6 +29,7 @@ import org.junit.Assert.*
import java.net.URI
import java.time.Duration
import java.time.Period
+import java.util.TimeZone
class AndroidEventTest {
@@ -1460,6 +1461,21 @@ class AndroidEventTest {
}
@Test
+ fun testPopulateEvent_NonAllDay_Recurring_Duration_KievTimeZone() {
+ populateEvent(false) {
+ put(Events.DTSTART, 1592733600000L) // 21/06/2020 18:00 +0800
+ put(Events.EVENT_TIMEZONE, "Europe/Kiev")
+ put(Events.DURATION, "PT1H")
+ put(Events.RRULE, "FREQ=DAILY;COUNT=2")
+ }.let { result ->
+ assertEquals(1592733600000L, result.dtStart?.date?.time)
+ assertEquals(1592733600000L + 3600000, result.dtEnd?.date?.time)
+ assertEquals("Europe/Kiev", result.dtStart?.timeZone?.id)
+ assertEquals("Europe/Kiev", result.dtEnd?.timeZone?.id)
+ }
+ }
+
+ @Test
fun testPopulateEvent_NonAllDay_NonRecurring_NoTime() {
populateEvent(false) {
put(Events.DTSTART, 1592742600000L) // 21/06/2020 14:30 +0200
diff --git a/src/main/java/at/bitfire/ical4android/AndroidCompatTimeZoneRegistry.kt b/src/main/java/at/bitfire/ical4android/AndroidCompatTimeZoneRegistry.kt
new file mode 100644
index 0000000..5f7246d
--- /dev/null
+++ b/src/main/java/at/bitfire/ical4android/AndroidCompatTimeZoneRegistry.kt
@@ -0,0 +1,83 @@
+package at.bitfire.ical4android
+
+import net.fortuna.ical4j.model.*
+import net.fortuna.ical4j.model.component.VTimeZone
+import net.fortuna.ical4j.model.property.TzId
+import java.time.ZoneId
+
+/**
+ * The purpose of this class is that if a time zone has a different name in ical4j and Android,
+ * it should use the Android name.
+ *
+ * For instance, if a time zone is known as "Europe/Kyiv" (with alias "Europe/Kiev") in ical4j
+ * and only "Europe/Kiev" in Android, this registry behaves like the default [TimeZoneRegistryImpl],
+ * but the returned time zone for `getTimeZone("Europe/Kiev")` has an ID of "Europe/Kiev" and not
+ * "Europe/Kyiv".
+ */
+class AndroidCompatTimeZoneRegistry(
+ private val base: TimeZoneRegistry
+): TimeZoneRegistry by base {
+
+ /**
+ * Gets the time zone for a given ID.
+ *
+ * If a time zone with the given ID exists in Android, the icalj timezone for this ID
+ * is returned, but the TZID is set to the Android name (and not the ical4j name, which
+ * may not be known to Android).
+ *
+ * If a time zone with the given ID doesn't exist in Android, this method returns the
+ * result of its [base] method.
+ *
+ * @param id
+ * @return time zone
+ */
+ override fun getTimeZone(id: String): TimeZone? {
+ // check whether time zone is available on Android
+ val androidTzId =
+ try {
+ ZoneId.of(id).id
+ } catch (e: Exception) {
+ /* Not available in Android, should return null in a later version.
+ However, we return the ical4j timezone to keep the changes caused by AndroidCompatTimeZoneRegistry introduction
+ as small as possible. */
+ return base.getTimeZone(id)
+ }
+
+ /* Time zone known by Android. Unfortunately, we can't use the Android timezone database directly
+ to generate ical4j timezone definitions (which are based on VTIMEZONE).
+ So we have to use the timezone definition from ical4j (based on its own VTIMEZONE database),
+ but we also need to use the Android TZ name (otherwise Android may not understand it later).
+
+ Example: getTimeZone("Europe/Kiev") returns a TimeZone with TZID:Europe/Kyiv since ical4j/3.2.5,
+ but most Android devices don't now Europe/Kyiv yet.
+ */
+ val tz = base.getTimeZone(id)
+ if (tz.id != androidTzId) {
+ Ical4Android.log.warning("Using Android TZID $androidTzId instead of ical4j ${tz.id}")
+
+ // create a copy of the VTIMEZONE so that we don't modify the original registry values (which are not immutable)
+ val vTimeZone = tz.vTimeZone
+ val newVTimeZoneProperties = PropertyList(vTimeZone.properties)
+ newVTimeZoneProperties.removeAll { property ->
+ property is TzId
+ }
+ newVTimeZoneProperties += TzId(androidTzId)
+ return TimeZone(VTimeZone(
+ newVTimeZoneProperties,
+ vTimeZone.observances
+ ))
+ } else
+ return tz
+ }
+
+
+ class Factory : TimeZoneRegistryFactory() {
+
+ override fun createRegistry(): TimeZoneRegistry {
+ val ical4jRegistry = DefaultTimeZoneRegistryFactory().createRegistry()
+ return AndroidCompatTimeZoneRegistry(ical4jRegistry)
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/at/bitfire/ical4android/AndroidEvent.kt b/src/main/java/at/bitfire/ical4android/AndroidEvent.kt
index 8648dd5..7c354a6 100644
--- a/src/main/java/at/bitfire/ical4android/AndroidEvent.kt
+++ b/src/main/java/at/bitfire/ical4android/AndroidEvent.kt
@@ -228,6 +228,7 @@ abstract class AndroidEvent(
}
}
event.dtStart = DtStart(dtStartDateTime)
+ AndroidTimeUtils.androidifyTimeZone(event.dtStart) // because it may have an ical4j timezone ID that is not available in Android
// Android events MUST have duration or dtend [https://developer.android.com/reference/android/provider/CalendarContract.Events#operations].
// Assume 1 hour if missing (should never occur, but occurs).
diff --git a/src/main/java/at/bitfire/ical4android/util/AndroidTimeUtils.kt b/src/main/java/at/bitfire/ical4android/util/AndroidTimeUtils.kt
index 337ab41..ec4388a 100644
--- a/src/main/java/at/bitfire/ical4android/util/AndroidTimeUtils.kt
+++ b/src/main/java/at/bitfire/ical4android/util/AndroidTimeUtils.kt
@@ -104,6 +104,8 @@ object AndroidTimeUtils {
* Returns the time-zone ID for a given date or date-time that should be used to store it
* in the Android calendar provider.
*
+ * Does not check whether Android actually knows the time zone ID – use [androidifyTimeZone] for that.
+ *
* @param date DateProperty (DATE or DATE-TIME) whose time-zone information is used
*
* @return - UTC for dates and UTC date-times
diff --git a/src/main/resources/ical4j.properties b/src/main/resources/ical4j.properties
index dfa5830..5a83b9f 100644
--- a/src/main/resources/ical4j.properties
+++ b/src/main/resources/ical4j.properties
@@ -1,4 +1,5 @@
net.fortuna.ical4j.timezone.cache.impl=net.fortuna.ical4j.util.MapTimeZoneCache
+net.fortuna.ical4j.timezone.registry=at.bitfire.ical4android.AndroidCompatTimeZoneRegistry$Factory
net.fortuna.ical4j.timezone.update.enabled=false
ical4j.parsing.relaxed=true
ical4j.unfolding.relaxed=true