Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Commit

Permalink
replace JodaTime in analytics (EXPOSUREAPP-13114) (#5199)
Browse files Browse the repository at this point in the history
* replace JodaTime in analytics

* detekt

* remove unused code

* add year comparison

* return timezones

* ktlint

* use ChronoUnit to calculate days

Co-authored-by: BMItr <Berndus@gmx.de>
Co-authored-by: Marc Auberer <marc.auberer@chillibits.com>
Co-authored-by: Mohamed Metwalli <mohamed.metwalli@sap.com>
  • Loading branch information
4 people authored and chiljamgossow committed Jun 7, 2022
1 parent 2bab82e commit e8c9f1b
Show file tree
Hide file tree
Showing 39 changed files with 204 additions and 180 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import de.rki.coronawarnapp.util.TimeStamper
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.serialization.BaseGson
import de.rki.coronawarnapp.util.serialization.adapter.InstantAdapter
import de.rki.coronawarnapp.util.serialization.adapter.JavaInstantAdapter
import de.rki.coronawarnapp.util.serialization.fromJson
import de.rki.coronawarnapp.util.serialization.toJson
import kotlinx.coroutines.withContext
import okio.ByteString.Companion.decodeBase64
import okio.ByteString.Companion.toByteString
import org.joda.time.Instant
import java.time.Instant
import timber.log.Timber
import java.io.File
import javax.inject.Inject
Expand All @@ -34,7 +34,7 @@ class DefaultLastAnalyticsSubmissionLogger @Inject constructor(

private val gson by lazy {
baseGson.newBuilder()
.registerTypeAdapter(Instant::class.java, InstantAdapter())
.registerTypeAdapter(Instant::class.java, JavaInstantAdapter())
.registerTypeAdapter(PpaData.PPADataAndroid::class.java, PPADataAndroidAdapter())
.create()
}
Expand All @@ -46,7 +46,7 @@ class DefaultLastAnalyticsSubmissionLogger @Inject constructor(
}

val dataObject = LastAnalyticsSubmission(
timestamp = timeStamper.nowUTC,
timestamp = timeStamper.nowJavaUTC,
ppaDataAndroid = analyticsProto
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.datadonation

import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
import org.joda.time.Instant
import java.time.Instant
import java.util.UUID

@Keep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.datadonation
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
import de.rki.coronawarnapp.server.protocols.internal.ppdd.EdusOtp
import org.joda.time.Instant
import java.time.Instant
import java.util.UUID

@Keep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeout
import org.joda.time.Hours
import timber.log.Timber
import java.time.Duration
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.random.Random
Expand Down Expand Up @@ -142,7 +142,7 @@ class Analytics @Inject constructor(

if (result.successful) {
settings.lastSubmittedTimestamp.update {
timeStamper.nowUTC
timeStamper.nowJavaUTC
}

logger.storeAnalyticsData(analyticsProto)
Expand Down Expand Up @@ -171,14 +171,14 @@ class Analytics @Inject constructor(
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun stopDueToLastSubmittedTimestamp(): Boolean {
val lastSubmit = settings.lastSubmittedTimestamp.value ?: return false
return lastSubmit.plus(Hours.hours(LAST_SUBMISSION_MIN_AGE_HOURS).toStandardDuration())
.isAfter(timeStamper.nowUTC)
return lastSubmit.plus(Duration.ofHours(LAST_SUBMISSION_MIN_AGE_HOURS))
.isAfter(timeStamper.nowJavaUTC)
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun stopDueToTimeSinceOnboarding(): Boolean {
val onboarding = onboardingSettings.onboardingCompletedTimestamp.value ?: return true
return onboarding.plus(Hours.hours(ONBOARDING_DELAY_HOURS).toStandardDuration()).isAfter(timeStamper.nowUTC)
return onboarding.plus(Duration.ofHours(ONBOARDING_DELAY_HOURS)).isAfter(timeStamper.nowJavaUTC)
}

suspend fun submitIfWanted(): Result = submissionLockoutMutex.withLock {
Expand Down Expand Up @@ -244,8 +244,8 @@ class Analytics @Inject constructor(

companion object {
private val TAG = Analytics::class.java.simpleName
private const val LAST_SUBMISSION_MIN_AGE_HOURS = 23
private const val ONBOARDING_DELAY_HOURS = 24
private const val LAST_SUBMISSION_MIN_AGE_HOURS = 23L
private const val ONBOARDING_DELAY_HOURS = 24L

data class PPADeviceAttestationRequest(
val ppaData: PpaData.PPADataAndroid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package de.rki.coronawarnapp.datadonation.analytics.common
import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult
import de.rki.coronawarnapp.risk.EwRiskLevelResult
import de.rki.coronawarnapp.risk.RiskState
import org.joda.time.Days
import org.joda.time.Instant
import org.joda.time.LocalDate
import de.rki.coronawarnapp.util.toJavaInstant
import java.time.Instant
import java.time.LocalDate
import java.time.temporal.ChronoUnit

fun calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration(
lastDateAtRiskLevel: LocalDate?,
Expand All @@ -14,37 +15,34 @@ fun calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration(
testRegisteredAt ?: return -1
lastDateAtRiskLevel ?: return -1
if (lastDateAtRiskLevel.isAfter(testRegisteredAt)) return -1
return Days.daysBetween(
lastDateAtRiskLevel,
testRegisteredAt
).days
return ChronoUnit.DAYS.between(lastDateAtRiskLevel, testRegisteredAt).toInt()
}

fun List<PtRiskLevelResult>.getLastChangeToHighPtRiskBefore(testRegisteredAt: Instant): Instant? {
val successfulResults = filter { it.wasSuccessfullyCalculated }
.filter { it.calculatedAt <= testRegisteredAt }
.filter { it.calculatedAt.toJavaInstant() <= testRegisteredAt }
.sortedByDescending { it.calculatedAt }

successfulResults.forEachIndexed { index, ptRiskLevelResult ->
if (ptRiskLevelResult.riskState == RiskState.INCREASED_RISK &&
(index == successfulResults.lastIndex || successfulResults[index + 1].riskState == RiskState.LOW_RISK)
) {
return ptRiskLevelResult.calculatedAt
return ptRiskLevelResult.calculatedAt.toJavaInstant()
}
}
return null
}

fun List<EwRiskLevelResult>.getLastChangeToHighEwRiskBefore(testRegisteredAt: Instant): Instant? {
val successfulResults = filter { it.wasSuccessfullyCalculated }
.filter { it.calculatedAt <= testRegisteredAt }
.filter { it.calculatedAt.toJavaInstant() <= testRegisteredAt }
.sortedByDescending { it.calculatedAt }

successfulResults.forEachIndexed { index, ptRiskLevelResult ->
if (ptRiskLevelResult.riskState == RiskState.INCREASED_RISK &&
(index == successfulResults.lastIndex || successfulResults[index + 1].riskState == RiskState.LOW_RISK)
) {
return ptRiskLevelResult.calculatedAt
return ptRiskLevelResult.calculatedAt.toJavaInstant()
}
}
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.exposurewindows

import androidx.annotation.VisibleForTesting
import de.rki.coronawarnapp.util.TimeStamper
import org.joda.time.Days
import java.time.Duration
import javax.inject.Inject
import javax.inject.Singleton

Expand Down Expand Up @@ -45,7 +45,7 @@ class AnalyticsExposureWindowRepository @Inject constructor(
}

suspend fun deleteStaleData() {
val timestamp = timeStamper.nowUTC.minus(Days.days(15).toStandardDuration()).millis
val timestamp = timeStamper.nowJavaUTC.minus(Duration.ofDays(15)).toEpochMilli()
dao.deleteReportedOlderThan(timestamp)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings
import de.rki.coronawarnapp.risk.RiskState
import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc
import de.rki.coronawarnapp.util.TimeStamper
import de.rki.coronawarnapp.util.toJavaInstant
import de.rki.coronawarnapp.util.toJavaTime
import de.rki.coronawarnapp.util.toLocalDateUtc
import kotlinx.coroutines.flow.first
import org.joda.time.Duration
import java.time.Duration
import javax.inject.Inject

class AnalyticsKeySubmissionCollector @Inject constructor(
Expand All @@ -32,14 +34,14 @@ class AnalyticsKeySubmissionCollector @Inject constructor(
if (disabled) return
// do not overwrite once set
if (type.storage.testResultReceivedAt.value > 0) return
type.storage.testResultReceivedAt.update { timeStamper.nowUTC.millis }
type.storage.testResultReceivedAt.update { timeStamper.nowJavaUTC.toEpochMilli() }
}

suspend fun reportTestRegistered(type: BaseCoronaTest.Type) {
if (disabled) return

val testRegisteredAt = timeStamper.nowUTC
type.storage.testRegisteredAt.update { testRegisteredAt.millis }
val testRegisteredAt = timeStamper.nowJavaUTC
type.storage.testRegisteredAt.update { testRegisteredAt.toEpochMilli() }

val lastResult = riskLevelStorage
.latestAndLastSuccessfulCombinedEwPtRiskLevelResult
Expand All @@ -51,10 +53,10 @@ class AnalyticsKeySubmissionCollector @Inject constructor(
.first()
.getLastChangeToHighEwRiskBefore(testRegisteredAt)
?.let {
val hours = Duration(
val hours = Duration.between(
it,
testRegisteredAt
).standardHours.toInt()
).toHours().toInt()
type.storage.ewHoursSinceHighRiskWarningAtTestRegistration.update {
hours
}
Expand All @@ -66,10 +68,10 @@ class AnalyticsKeySubmissionCollector @Inject constructor(
.first()
.getLastChangeToHighPtRiskBefore(testRegisteredAt)
?.let {
val hours = Duration(
val hours = Duration.between(
it,
testRegisteredAt
).standardHours.toInt()
).toHours().toInt()
type.storage.ptHoursSinceHighRiskWarningAtTestRegistration.update {
hours
}
Expand All @@ -78,14 +80,14 @@ class AnalyticsKeySubmissionCollector @Inject constructor(

type.storage.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update {
calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration(
lastResult.ewRiskLevelResult.mostRecentDateAtRiskState?.toLocalDateUtc(),
lastResult.ewRiskLevelResult.mostRecentDateAtRiskState?.toJavaInstant()?.toLocalDateUtc(),
testRegisteredAt.toLocalDateUtc()
)
}

type.storage.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update {
calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration(
lastResult.ptRiskLevelResult.mostRecentDateAtRiskState,
lastResult.ptRiskLevelResult.mostRecentDateAtRiskState?.toJavaTime(),
testRegisteredAt.toLocalDateUtc()
)
}
Expand All @@ -94,7 +96,7 @@ class AnalyticsKeySubmissionCollector @Inject constructor(
fun reportSubmitted(type: BaseCoronaTest.Type) {
if (disabled) return
type.storage.submitted.update { true }
type.storage.submittedAt.update { timeStamper.nowUTC.millis }
type.storage.submittedAt.update { timeStamper.nowJavaUTC.toEpochMilli() }
}

fun reportSubmittedInBackground(type: BaseCoronaTest.Type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule
import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
import de.rki.coronawarnapp.server.protocols.internal.ppdd.TriStateBooleanOuterClass
import de.rki.coronawarnapp.util.TimeStamper
import org.joda.time.Duration
import org.joda.time.Instant
import java.time.Duration
import java.time.Instant
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -28,7 +28,7 @@ abstract class AnalyticsKeySubmissionDonor(
) : DonorModule {
override suspend fun beginDonation(request: DonorModule.Request): DonorModule.Contribution {
val hours = request.currentConfig.analytics.hoursSinceTestResultToSubmitKeySubmissionMetadata
val timeSinceTestResultToSubmit = Duration.standardHours(hours.toLong())
val timeSinceTestResultToSubmit = Duration.ofHours(hours.toLong())
return if (shouldSubmitData(timeSinceTestResultToSubmit)) {
object : DonorModule.Contribution {
override suspend fun injectData(protobufContainer: PpaData.PPADataAndroid.Builder) {
Expand Down Expand Up @@ -85,7 +85,9 @@ abstract class AnalyticsKeySubmissionDonor(

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun enoughTimeHasPassedSinceResult(timeSinceTestResultToSubmit: Duration): Boolean =
timeStamper.nowUTC.minus(timeSinceTestResultToSubmit) > Instant.ofEpochMilli(repository.testResultReceivedAt)
timeStamper
.nowJavaUTC
.minus(timeSinceTestResultToSubmit) > Instant.ofEpochMilli(repository.testResultReceivedAt)

override suspend fun deleteData() {
repository.reset()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission

import org.joda.time.Duration
import java.time.Duration
import javax.inject.Inject
import javax.inject.Singleton

Expand Down Expand Up @@ -58,15 +58,15 @@ abstract class AnalyticsKeySubmissionRepository(
if (submittedAt <= 0) return -1
if (testResultReceivedAt <= 0) return -1
if (submittedAt < testResultReceivedAt) return -1
return Duration.millis(submittedAt - testResultReceivedAt).toStandardHours().hours
return Duration.ofMillis(submittedAt - testResultReceivedAt).toHours().toInt()
}

val hoursSinceTestRegistration: Int
get() {
if (submittedAt <= 0) return -1
if (testRegisteredAt <= 0) return -1
if (submittedAt < testRegisteredAt) return -1
return Duration.millis(submittedAt - testRegisteredAt).toStandardHours().hours
return Duration.ofMillis(submittedAt - testRegisteredAt).toHours().toInt()
}

val ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration: Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ import de.rki.coronawarnapp.risk.result.RiskResult
import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc
import de.rki.coronawarnapp.util.TimeStamper
import de.rki.coronawarnapp.util.toJavaInstant
import de.rki.coronawarnapp.util.toJavaTime
import de.rki.coronawarnapp.util.toLocalDateUtc
import kotlinx.coroutines.flow.first
import org.joda.time.Duration
import java.time.Duration
import timber.log.Timber
import javax.inject.Inject

Expand All @@ -46,7 +49,7 @@ class AnalyticsTestResultCollector @Inject constructor(
suspend fun reportTestRegistered(type: BaseCoronaTest.Type) {
if (analyticsDisabled) return

val testRegisteredAt = timeStamper.nowUTC
val testRegisteredAt = timeStamper.nowJavaUTC
type.settings.testRegisteredAt.update { testRegisteredAt }

val lastResult = riskLevelStorage
Expand All @@ -56,14 +59,14 @@ class AnalyticsTestResultCollector @Inject constructor(

type.settings.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update {
calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration(
lastResult.ewRiskLevelResult.mostRecentDateAtRiskState?.toLocalDateUtc(),
lastResult.ewRiskLevelResult.mostRecentDateAtRiskState?.toJavaInstant()?.toLocalDateUtc(),
testRegisteredAt.toLocalDateUtc()
)
}

type.settings.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update {
calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration(
lastResult.ptRiskLevelResult.mostRecentDateAtRiskState,
lastResult.ptRiskLevelResult.mostRecentDateAtRiskState?.toJavaTime(),
testRegisteredAt.toLocalDateUtc()
)
}
Expand All @@ -72,10 +75,10 @@ class AnalyticsTestResultCollector @Inject constructor(
riskLevelStorage.allEwRiskLevelResults
.first()
.getLastChangeToHighEwRiskBefore(testRegisteredAt)?.let {
val hours = Duration(
val hours = Duration.between(
it,
testRegisteredAt
).standardHours.toInt()
).toHours().toInt()
type.settings.ewHoursSinceHighRiskWarningAtTestRegistration.update {
hours
}
Expand All @@ -86,10 +89,10 @@ class AnalyticsTestResultCollector @Inject constructor(
riskLevelStorage.allPtRiskLevelResults
.first()
.getLastChangeToHighPtRiskBefore(testRegisteredAt)?.let {
val hours = Duration(
val hours = Duration.between(
it,
testRegisteredAt
).standardHours.toInt()
).toHours().toInt()
type.settings.ptHoursSinceHighRiskWarningAtTestRegistration.update {
hours
}
Expand Down Expand Up @@ -128,7 +131,7 @@ class AnalyticsTestResultCollector @Inject constructor(
type.settings.testResult.update { testResult }

if (testResult.isFinalResult && type.settings.finalTestResultReceivedAt.value == null) {
type.settings.finalTestResultReceivedAt.update { timeStamper.nowUTC }
type.settings.finalTestResultReceivedAt.update { timeStamper.nowJavaUTC }

val newExposureWindows = exposureWindowsSettings.currentExposureWindows.value?.filterExposureWindows(
type.settings.exposureWindowsAtTestRegistration.value
Expand Down
Loading

0 comments on commit e8c9f1b

Please sign in to comment.