diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/derivetime/TimeIntervalDeriver.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/derivetime/TimeIntervalDeriver.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ddde8f6ad28d8c3c80b6c9b27f5e2f8e3459ac13
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/derivetime/TimeIntervalDeriver.kt
@@ -0,0 +1,77 @@
+package de.rki.coronawarnapp.eventregistration.checkins.derivetime
+
+import de.rki.coronawarnapp.appconfig.PresenceTracingSubmissionParamContainer
+import de.rki.coronawarnapp.risk.DefaultRiskLevels.Companion.inRange
+import timber.log.Timber
+import java.util.concurrent.TimeUnit
+import kotlin.math.max
+import kotlin.math.roundToLong
+
+private val INTERVAL_LENGTH_IN_SECONDS = TimeUnit.MINUTES.toSeconds(10L)
+
+private fun alignToInterval(timestamp: Long) =
+    (timestamp / INTERVAL_LENGTH_IN_SECONDS) * INTERVAL_LENGTH_IN_SECONDS
+
+/**
+ * Derive CheckIn start and end times
+ * @param startTimestampInSeconds [Long] timestamp in seconds
+ * @param endTimestampInSeconds [Long] timestamp in seconds
+ */
+fun PresenceTracingSubmissionParamContainer.deriveTime(
+    startTimestampInSeconds: Long,
+    endTimestampInSeconds: Long
+): Pair<Long, Long>? {
+    val durationInSeconds = max(0, endTimestampInSeconds - startTimestampInSeconds)
+    Timber.d("durationInSeconds: $durationInSeconds")
+
+    val durationInMinutes = TimeUnit.SECONDS.toMinutes(durationInSeconds)
+    Timber.d("durationInMinutes: $durationInMinutes")
+
+    val dropDueToDuration: Boolean = durationFilters.any { durationFilter ->
+        durationFilter.dropIfMinutesInRange.inRange(durationInMinutes)
+    }
+    Timber.d("dropDueToDuration: $dropDueToDuration")
+    if (dropDueToDuration) return null
+
+    val aerosoleDecays: List<Double> = aerosoleDecayLinearFunctions.filter { aerosole ->
+        aerosole.minutesRange.inRange(durationInMinutes)
+    }.map { aerosole ->
+        aerosole.slope * durationInSeconds + TimeUnit.MINUTES.toSeconds(aerosole.intercept.toLong())
+    }
+    Timber.d("aerosoleDecays:$aerosoleDecays")
+    val aerosoleDecayInSeconds: Double = aerosoleDecays.firstOrNull() ?: 0.0 // Default: zero, i.e. 'no decay'
+    Timber.d("aerosoleDecayInSeconds: $aerosoleDecayInSeconds")
+
+    val relevantEndTimestamp = endTimestampInSeconds + aerosoleDecayInSeconds.toLong()
+    val relevantStartIntervalTimestamp = alignToInterval(startTimestampInSeconds)
+    val relevantEndIntervalTimestamp = alignToInterval(relevantEndTimestamp)
+    val overlapWithStartInterval = relevantStartIntervalTimestamp + INTERVAL_LENGTH_IN_SECONDS - startTimestampInSeconds
+    val overlapWithEndInterval = relevantEndTimestamp - relevantEndIntervalTimestamp
+    Timber.d("overlapWithStartInterval: $overlapWithStartInterval")
+    Timber.d("overlapWithEndInterval: $overlapWithEndInterval")
+
+    val targetDurationInSeconds =
+        ((durationInSeconds + aerosoleDecayInSeconds) / INTERVAL_LENGTH_IN_SECONDS).roundToLong() *
+            INTERVAL_LENGTH_IN_SECONDS
+
+    Timber.d("targetDurationInSeconds:$targetDurationInSeconds")
+
+    return if (overlapWithEndInterval > overlapWithStartInterval) {
+        Timber.d(
+            "overlapWithEndInterval:%s > overlapWithStartInterval:%s",
+            overlapWithEndInterval,
+            overlapWithStartInterval
+        )
+        val newEndTimestamp = relevantEndIntervalTimestamp + INTERVAL_LENGTH_IN_SECONDS
+        val newStartTimestamp = newEndTimestamp - targetDurationInSeconds
+        newStartTimestamp to newEndTimestamp
+    } else {
+        Timber.d(
+            "overlapWithEndInterval:%s, overlapWithStartInterval:%s",
+            overlapWithEndInterval,
+            overlapWithStartInterval
+        )
+        val newEndTimestamp = relevantStartIntervalTimestamp + targetDurationInSeconds
+        relevantStartIntervalTimestamp to newEndTimestamp
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt
index 85fd30cce117c6ae076783472a42c243834f098e..b7d4b469423c17b89646fb7e7d290469b4a38fe1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/DefaultRiskLevels.kt
@@ -310,7 +310,7 @@ class DefaultRiskLevels @Inject constructor() : RiskLevels {
             "The Report Type returned by the ENF is not known"
         )
 
-        private fun <T : Number> RiskCalculationParametersOuterClass.Range.inRange(value: T): Boolean =
+        fun <T : Number> RiskCalculationParametersOuterClass.Range.inRange(value: T): Boolean =
             when {
                 minExclusive && value.toDouble() <= min -> false
                 !minExclusive && value.toDouble() < min -> false
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/derivetime/TimeIntervalDeriverTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/derivetime/TimeIntervalDeriverTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..eed36da372cf26fa9bd3e83fcdc3efe225e5df97
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/derivetime/TimeIntervalDeriverTest.kt
@@ -0,0 +1,281 @@
+package de.rki.coronawarnapp.eventregistration.checkins.derivetime
+
+import de.rki.coronawarnapp.appconfig.PresenceTracingSubmissionParamContainer
+import de.rki.coronawarnapp.server.protocols.internal.v2
+    .PresenceTracingParametersOuterClass.PresenceTracingSubmissionParameters.DurationFilter
+import de.rki.coronawarnapp.server.protocols.internal.v2
+    .PresenceTracingParametersOuterClass.PresenceTracingSubmissionParameters.AerosoleDecayFunctionLinear
+import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass.Range
+import io.kotest.matchers.shouldBe
+import org.joda.time.DateTime
+import org.joda.time.DateTimeZone
+import org.joda.time.format.DateTimeFormat
+import org.joda.time.format.DateTimeFormatter
+import org.junit.jupiter.api.Test
+
+import testhelpers.BaseTest
+import java.util.concurrent.TimeUnit
+
+/**
+ * Test scenarios reference: [https://github.com/corona-warn-app/cwa-app-tech-spec/blob/
+ * proposal/event-registration-mvp/test-cases/pt-derive-time-interval-data.json]
+ */
+internal class TimeIntervalDeriverTest : BaseTest() {
+
+    /* "defaultConfiguration": {
+    "durationFilters": [
+      {
+        "dropIfDurationInRange": {
+          "min": 0,
+          "max": 10,
+          "maxExclusive": true
+        }
+      }
+    ],
+    "aerosoleDecayTime": [
+      {
+        "durationRange": {
+          "min": 0,
+          "max": 30
+        },
+        "slope": 1,
+        "intercept": 0
+      },
+      {
+        "durationRange": {
+          "min": 30,
+          "max": 9999,
+          "minExclusive": true
+        },
+        "slope": 0,
+        "intercept": 30
+      }
+    ]
+  } */
+
+    private val timeFormat: DateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm Z")
+        .withZone(DateTimeZone.forID("Europe/Berlin"))
+
+    private val presenceTracingConfig = PresenceTracingSubmissionParamContainer(
+        durationFilters = listOf(
+            DurationFilter.newBuilder()
+                .setDropIfMinutesInRange(
+                    Range.newBuilder()
+                        .setMin(0.0)
+                        .setMax(10.0)
+                        .setMaxExclusive(true)
+                        .build()
+                )
+                .build()
+        ),
+        aerosoleDecayLinearFunctions = listOf(
+            AerosoleDecayFunctionLinear.newBuilder()
+                .setMinutesRange(
+                    Range.newBuilder()
+                        .setMin(0.0)
+                        .setMax(30.0)
+                        .build()
+                )
+                .setSlope(1.0)
+                .setIntercept(0.0)
+                .build(),
+            AerosoleDecayFunctionLinear.newBuilder()
+                .setMinutesRange(
+                    Range.newBuilder()
+                        .setMin(30.0)
+                        .setMax(9999.0)
+                        .setMinExclusive(true)
+                        .build()
+                )
+                .setSlope(0.0)
+                .setIntercept(30.0)
+                .build()
+        )
+    )
+
+    @Test
+    fun `Scenario 1`() {
+        /*
+        "description": "Scenario 1",
+        "startDateStr": "2021-03-04 10:21+01:00",
+        "endDateStr": "2021-03-04 10:29+01:00",
+        "expStartDateStr": null,
+        "expEndDateStr": null
+        */
+        presenceTracingConfig.deriveTime(
+            timeInSeconds("2021-03-04 10:21 +01:00"),
+            timeInSeconds("2021-03-04 10:29 +01:00")
+        ) shouldBe null
+    }
+
+    @Test
+    fun `Scenario 2`() {
+        /*
+         "description": "Scenario 2",
+         "startDateStr": "2021-03-04 10:20+01:00",
+         "endDateStr": "2021-03-04 10:30+01:00",
+         "expStartDateStr": "2021-03-04 10:20+01:00",
+         "expEndDateStr": "2021-03-04 10:40+01:00"
+         */
+        val (startTime, endTime) = presenceTracingConfig.deriveTime(
+            timeInSeconds("2021-03-04 10:20 +01:00"),
+            timeInSeconds("2021-03-04 10:30 +01:00")
+        )!!
+
+        timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
+        timeToString(endTime) shouldBe "2021-03-04 10:40 +0100"
+    }
+
+    @Test
+    fun `Scenario 3`() {
+        /*
+         "description": "Scenario 3",
+         "startDateStr": "2021-03-04 10:21+01:00",
+         "endDateStr": "2021-03-04 10:31+01:00",
+         "expStartDateStr": "2021-03-04 10:20+01:00",
+         "expEndDateStr": "2021-03-04 10:40+01:00"
+         */
+        val (startTime, endTime) = presenceTracingConfig.deriveTime(
+            timeInSeconds("2021-03-04 10:21 +01:00"),
+            timeInSeconds("2021-03-04 10:31 +01:00")
+        )!!
+
+        timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
+        timeToString(endTime) shouldBe "2021-03-04 10:40 +0100"
+    }
+
+    @Test
+    fun `Scenario 4`() {
+        /*
+         "description": "Scenario 4",
+         "startDateStr": "2021-03-04 10:26+01:00",
+         "endDateStr": "2021-03-04 10:36+01:00",
+         "expStartDateStr": "2021-03-04 10:30+01:00",
+         "expEndDateStr": "2021-03-04 10:50+01:00"
+         */
+        val (startTime, endTime) = presenceTracingConfig.deriveTime(
+            timeInSeconds("2021-03-04 10:26 +01:00"),
+            timeInSeconds("2021-03-04 10:36 +01:00")
+        )!!
+
+        timeToString(startTime) shouldBe "2021-03-04 10:30 +0100"
+        timeToString(endTime) shouldBe "2021-03-04 10:50 +0100"
+    }
+
+    @Test
+    fun `Scenario 5`() {
+        /*
+          "description": "Scenario 5",
+          "startDateStr": "2021-03-04 10:21+01:00",
+          "endDateStr": "2021-03-04 10:33+01:00",
+          "expStartDateStr": "2021-03-04 10:20+01:00",
+          "expEndDateStr": "2021-03-04 10:40+01:00"
+         */
+        val (startTime, endTime) = presenceTracingConfig.deriveTime(
+            timeInSeconds("2021-03-04 10:21 +01:00"),
+            timeInSeconds("2021-03-04 10:33 +01:00")
+        )!!
+
+        timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
+        timeToString(endTime) shouldBe "2021-03-04 10:40 +0100"
+    }
+
+    @Test
+    fun `Scenario 6`() {
+        /*
+          "description": "Scenario 6",
+          "startDateStr": "2021-03-04 10:24+01:00",
+          "endDateStr": "2021-03-04 10:36+01:00",
+          "expStartDateStr": "2021-03-04 10:30+01:00",
+          "expEndDateStr": "2021-03-04 10:50+01:00"
+         */
+        val (startTime, endTime) = presenceTracingConfig.deriveTime(
+            timeInSeconds("2021-03-04 10:24 +01:00"),
+            timeInSeconds("2021-03-04 10:36 +01:00")
+        )!!
+
+        timeToString(startTime) shouldBe "2021-03-04 10:30 +0100"
+        timeToString(endTime) shouldBe "2021-03-04 10:50 +0100"
+    }
+
+    @Test
+    fun `Scenario 7`() {
+        /*
+          "description": "Scenario 7",
+          "startDateStr": "2021-03-04 10:25+01:00",
+          "endDateStr": "2021-03-04 10:39+01:00",
+          "expStartDateStr": "2021-03-04 10:20+01:00",
+          "expEndDateStr": "2021-03-04 10:50+01:00"
+         */
+        val (startTime, endTime) = presenceTracingConfig.deriveTime(
+            timeInSeconds("2021-03-04 10:25 +01:00"),
+            timeInSeconds("2021-03-04 10:39 +01:00")
+        )!!
+
+        timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
+        timeToString(endTime) shouldBe "2021-03-04 10:50 +0100"
+    }
+
+    @Test
+    fun `Scenario 8`() {
+        /*
+          "description": "Scenario 8",
+          "startDateStr": "2021-03-04 10:28+01:00",
+          "endDateStr": "2021-03-04 10:42+01:00",
+          "expStartDateStr": "2021-03-04 10:30+01:00",
+          "expEndDateStr": "2021-03-04 11:00+01:00"
+         */
+        val (startTime, endTime) = presenceTracingConfig.deriveTime(
+            timeInSeconds("2021-03-04 10:28 +01:00"),
+            timeInSeconds("2021-03-04 10:42 +01:00")
+        )!!
+
+        timeToString(startTime) shouldBe "2021-03-04 10:30 +0100"
+        timeToString(endTime) shouldBe "2021-03-04 11:00 +0100"
+    }
+
+    @Test
+    fun `Scenario 9`() {
+        /*
+          "description": "Scenario 9",
+          "startDateStr": "2021-03-04 10:25+01:00",
+          "endDateStr": "2021-03-04 10:40+01:00",
+          "expStartDateStr": "2021-03-04 10:20+01:00",
+          "expEndDateStr": "2021-03-04 10:50+01:00"
+         */
+        val (startTime, endTime) = presenceTracingConfig.deriveTime(
+            timeInSeconds("2021-03-04 10:25 +01:00"),
+            timeInSeconds("2021-03-04 10:40 +01:00")
+        )!!
+
+        timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
+        timeToString(endTime) shouldBe "2021-03-04 10:50 +0100"
+    }
+
+    @Test
+    fun `Scenario 10`() {
+        /*
+          "description": "Scenario 10",
+          "startDateStr": "2021-03-04 10:22+01:00",
+          "endDateStr": "2021-03-04 11:12+01:00",
+          "expStartDateStr": "2021-03-04 10:20+01:00",
+          "expEndDateStr": "2021-03-04 11:40+01:00"
+         */
+        val (startTime, endTime) = presenceTracingConfig.deriveTime(
+            timeInSeconds("2021-03-04 10:22 +01:00"),
+            timeInSeconds("2021-03-04 11:12 +01:00")
+        )!!
+
+        timeToString(startTime) shouldBe "2021-03-04 10:20 +0100"
+        timeToString(endTime) shouldBe "2021-03-04 11:40 +0100"
+    }
+
+    private fun timeInSeconds(dateTime: String): Long {
+        val millis = timeFormat.parseDateTime(dateTime).millis
+        return TimeUnit.MILLISECONDS.toSeconds(millis)
+    }
+
+    private fun timeToString(timeInSecond: Long): String {
+        return DateTime(TimeUnit.SECONDS.toMillis(timeInSecond)).toString(timeFormat)
+    }
+}