calculated day's difference over DST correctly

This commit is contained in:
Milos Kozak 2023-11-03 14:03:57 +01:00
parent a8fc73c8ad
commit 47758b6b27
11 changed files with 117 additions and 79 deletions

View file

@ -1,64 +1,86 @@
package app.aaps.core.interfaces.utils
import android.util.LongSparseArray
import java.util.Calendar
import androidx.annotation.VisibleForTesting
import java.time.Instant
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
object MidnightTime {
@VisibleForTesting
val times = LongSparseArray<Long>()
private var hits: Long = 0
private var misses: Long = 0
private const val THRESHOLD = 100000
fun calc(): Long {
val c = Calendar.getInstance()
c[Calendar.HOUR_OF_DAY] = 0
c[Calendar.MINUTE] = 0
c[Calendar.SECOND] = 0
c[Calendar.MILLISECOND] = 0
return c.timeInMillis
}
/**
* Epoch time of last midnight
*
* @return epoch millis
*/
fun calc(): Long =
LocalDateTime.now().atZone(ZoneId.systemDefault())
.with(LocalTime.of(0, 0, 0, 0))
.toInstant().toEpochMilli()
fun calcPlusMinutes(minutes: Int): Long {
val h = minutes / 60
/**
* Today's time with 'minutes' from midnight
*
* @param minutes minutes to add
* @return epoch millis of today with hh:mm:00
*/
fun calcMidnightPlusMinutes(minutes: Int): Long {
val h = (minutes / 60) % 24
val m = minutes % 60
val c = Calendar.getInstance()
c[Calendar.HOUR_OF_DAY] = h
c[Calendar.MINUTE] = m
c[Calendar.SECOND] = 0
c[Calendar.MILLISECOND] = 0
return c.timeInMillis
return LocalDateTime.now().atZone(ZoneId.systemDefault())
.with(LocalTime.of(h, m, 0, 0))
.toInstant().toEpochMilli()
}
/**
* Epoch time of last midnight before 'time'
*
* @param time time of the day
* @return epoch millis
*/
fun calc(time: Long): Long {
var m: Long?
synchronized(times) {
m = times[time]
if (m != null) {
++hits
return m!!
}
val c = Calendar.getInstance()
c.timeInMillis = time
c[Calendar.HOUR_OF_DAY] = 0
c[Calendar.MINUTE] = 0
c[Calendar.SECOND] = 0
c[Calendar.MILLISECOND] = 0
m = c.timeInMillis
times.append(time, m)
++misses
val m = times[time] ?: Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault())
.with(LocalTime.of(0, 0, 0, 0))
.toInstant().toEpochMilli()
if (times.size() > THRESHOLD) resetCache()
return m
}
return m!!
}
/**
* Epoch time of last midnight 'days' back
*
* @param daysBack how many days back
* @return epoch millis of midnight
*/
fun calcDaysBack(daysBack: Long): Long =
LocalDateTime.now().atZone(ZoneId.systemDefault())
.with(LocalTime.of(0, 0, 0, 0))
.minusDays(daysBack)
.toInstant().toEpochMilli()
/**
* Epoch time of last midnight 'days' back from time
*
* @param time start time
* @param daysBack how many days back
* @return epoch millis of midnight
*/
fun calcDaysBack(time: Long, daysBack: Long): Long =
Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault())
.with(LocalTime.of(0, 0, 0, 0))
.minusDays(daysBack)
.toInstant().toEpochMilli()
@VisibleForTesting
fun resetCache() {
hits = 0
misses = 0
times.clear()
}
fun log(): String =
"Hits: " + hits + " misses: " + misses + " stored: " + times.size()
}

View file

@ -25,24 +25,40 @@ class MidnightTimeTest {
assertThat(midnight).isAtMost(now)
val c = Calendar.getInstance()
c.timeInMillis = MidnightTime.calc(now)
assertThat(c[Calendar.HOUR_OF_DAY].toLong()).isEqualTo(0L)
assertThat(c[Calendar.MINUTE].toLong()).isEqualTo(0L)
assertThat(c[Calendar.SECOND].toLong()).isEqualTo(0L)
assertThat(c[Calendar.MILLISECOND].toLong()).isEqualTo(0L)
assertThat(c[Calendar.HOUR_OF_DAY]).isEqualTo(0)
assertThat(c[Calendar.MINUTE]).isEqualTo(0)
assertThat(c[Calendar.SECOND]).isEqualTo(0)
assertThat(c[Calendar.MILLISECOND]).isEqualTo(0)
// Assure we get the same time from cache
assertThat(midnight).isEqualTo(MidnightTime.calc(now))
}
@Test fun calcMidnightPlusMinutesTest() {
val c = Calendar.getInstance()
c.timeInMillis = MidnightTime.calcMidnightPlusMinutes(121)
assertThat(c[Calendar.HOUR_OF_DAY]).isEqualTo(2)
assertThat(c[Calendar.MINUTE]).isEqualTo(1)
assertThat(c[Calendar.SECOND]).isEqualTo(0)
assertThat(c[Calendar.MILLISECOND]).isEqualTo(0)
}
@Test fun calcDaysBackTest() {
// We get real midnight
val now = System.currentTimeMillis()
val c = Calendar.getInstance()
c.timeInMillis = MidnightTime.calc(now)
c.add(Calendar.DAY_OF_MONTH, -5)
assertThat(c[Calendar.HOUR_OF_DAY]).isEqualTo(0)
assertThat(c[Calendar.MINUTE]).isEqualTo(0)
assertThat(c[Calendar.SECOND]).isEqualTo(0)
assertThat(c[Calendar.MILLISECOND]).isEqualTo(0)
assertThat(MidnightTime.calcDaysBack(5)).isEqualTo(c.timeInMillis)
}
@Test fun resetCache() {
val now = System.currentTimeMillis()
MidnightTime.calc(now)
MidnightTime.resetCache()
assertThat(MidnightTime.times.size().toLong()).isEqualTo(0L)
}
@Test fun log() {
val now = System.currentTimeMillis()
MidnightTime.calc(now)
assertThat(MidnightTime.log()).startsWith("Hits:")
}
}

View file

@ -9,7 +9,6 @@ import app.aaps.core.interfaces.stats.DexcomTIR
import app.aaps.core.interfaces.stats.DexcomTirCalculator
import app.aaps.core.interfaces.utils.DateUtil
import app.aaps.core.interfaces.utils.MidnightTime
import app.aaps.core.interfaces.utils.T
import app.aaps.database.impl.AppRepository
import javax.inject.Inject
import javax.inject.Singleton
@ -24,7 +23,7 @@ class DexcomTirCalculatorImpl @Inject constructor(
val days = 14L
override fun calculate(): DexcomTIR {
val startTime = MidnightTime.calc(dateUtil.now() - T.days(days).msecs())
val startTime = MidnightTime.calcDaysBack(days)
val endTime = MidnightTime.calc(dateUtil.now())
val bgReadings = repository.compatGetBgReadingsDataFromTime(startTime, endTime, true).blockingGet()

View file

@ -39,9 +39,8 @@ class TddCalculatorImpl @Inject constructor(
) : TddCalculator {
override fun calculate(days: Long, allowMissingDays: Boolean): LongSparseArray<TotalDailyDose>? {
var startTime = MidnightTime.calc(dateUtil.now() - T.days(days).msecs())
var startTime = MidnightTime.calcDaysBack(days)
val endTime = MidnightTime.calc(dateUtil.now())
//val stepSize = T.hours(24).msecs() // this is not true on DST change
val result = LongSparseArray<TotalDailyDose>()
// Try to load cached values

View file

@ -15,7 +15,6 @@ import app.aaps.core.interfaces.stats.TIR
import app.aaps.core.interfaces.stats.TirCalculator
import app.aaps.core.interfaces.utils.DateUtil
import app.aaps.core.interfaces.utils.MidnightTime
import app.aaps.core.interfaces.utils.T
import app.aaps.database.impl.AppRepository
import javax.inject.Inject
import javax.inject.Singleton
@ -31,7 +30,7 @@ class TirCalculatorImpl @Inject constructor(
override fun calculate(days: Long, lowMgdl: Double, highMgdl: Double): LongSparseArray<TIR> {
if (lowMgdl < 39) throw RuntimeException("Low below 39")
if (lowMgdl > highMgdl) throw RuntimeException("Low > High")
val startTime = MidnightTime.calc(dateUtil.now() - T.days(days).msecs())
val startTime = MidnightTime.calcDaysBack(days)
val endTime = MidnightTime.calc(dateUtil.now())
val bgReadings = repository.compatGetBgReadingsDataFromTime(startTime, endTime, true).blockingGet()

View file

@ -331,19 +331,22 @@ class AutotunePlugin @Inject constructor(
jsonSettings.put("tune_insulin_curve", false)
val peakTime: Int = insulinInterface.peak
if (insulinInterface.id === Insulin.InsulinType.OREF_ULTRA_RAPID_ACTING)
jsonSettings.put("curve", "ultra-rapid")
else if (insulinInterface.id === Insulin.InsulinType.OREF_RAPID_ACTING)
jsonSettings.put("curve", "rapid-acting")
else if (insulinInterface.id === Insulin.InsulinType.OREF_LYUMJEV) {
when {
insulinInterface.id === Insulin.InsulinType.OREF_ULTRA_RAPID_ACTING -> jsonSettings.put("curve", "ultra-rapid")
insulinInterface.id === Insulin.InsulinType.OREF_RAPID_ACTING -> jsonSettings.put("curve", "rapid-acting")
insulinInterface.id === Insulin.InsulinType.OREF_LYUMJEV -> {
jsonSettings.put("curve", "ultra-rapid")
jsonSettings.put("useCustomPeakTime", true)
jsonSettings.put("insulinPeakTime", peakTime)
} else if (insulinInterface.id === Insulin.InsulinType.OREF_FREE_PEAK) {
}
insulinInterface.id === Insulin.InsulinType.OREF_FREE_PEAK -> {
jsonSettings.put("curve", if (peakTime > 55) "rapid-acting" else "ultra-rapid")
jsonSettings.put("useCustomPeakTime", true)
jsonSettings.put("insulinPeakTime", peakTime)
}
}
jsonString = jsonSettings.toString(4).replace("\\/", "/")
} catch (e: JSONException) {
aapsLogger.error(LTag.AUTOTUNE, e.localizedMessage ?: e.toString())
@ -392,7 +395,7 @@ class AutotunePlugin @Inject constructor(
}
}
for (i in days.weekdays.indices) {
json.put(WeekDay.DayOfWeek.values()[i].name, days.weekdays[i])
json.put(WeekDay.DayOfWeek.entries[i].name, days.weekdays[i])
}
json.put("result", result)
json.put("updateButtonVisibility", updateButtonVisibility)
@ -429,7 +432,7 @@ class AutotunePlugin @Inject constructor(
}
}
for (i in days.weekdays.indices)
days.weekdays[i] = JsonHelper.safeGetBoolean(json, WeekDay.DayOfWeek.values()[i].name, true)
days.weekdays[i] = JsonHelper.safeGetBoolean(json, WeekDay.DayOfWeek.entries[i].name, true)
result = JsonHelper.safeGetString(json, "result", "")
updateButtonVisibility = JsonHelper.safeGetInt(json, "updateButtonVisibility")
lastRunSuccess = true
@ -440,8 +443,8 @@ class AutotunePlugin @Inject constructor(
fun calcDays(daysBack: Int): Int {
var endTime = MidnightTime.calc(dateUtil.now()) + autotuneStartHour * 60 * 60 * 1000L
if (endTime > dateUtil.now()) endTime -= T.days(1).msecs() // Check if 4 AM is before now
val startTime = endTime - daysBack * T.days(1).msecs()
if (endTime > dateUtil.now()) endTime = MidnightTime.calcDaysBack(1) // Check if 4 AM is before now
val startTime = MidnightTime.calcDaysBack(endTime, daysBack.toLong())
var result = 0
for (i in 0 until daysBack) {
if (days.isSet(startTime + i * T.days(1).msecs()))

View file

@ -57,7 +57,7 @@ class InputTime(private val rh: ResourceHelper, private val dateUtil: DateUtil)
})
}
private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcPlusMinutes(minutesSinceMidnight)
private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcMidnightPlusMinutes(minutesSinceMidnight)
private fun getMinSinceMidnight(time: Long): Int = MidnightUtils.secondsFromMidnight(time) / 60

View file

@ -83,7 +83,7 @@ class InputTimeRange(private val rh: ResourceHelper, private val dateUtil: DateU
})
}
private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcPlusMinutes(minutesSinceMidnight)
private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcMidnightPlusMinutes(minutesSinceMidnight)
private fun getMinSinceMidnight(time: Long): Int = MidnightUtils.secondsFromMidnight(time) / 60

View file

@ -50,7 +50,7 @@ class TriggerRecurringTime(injector: HasAndroidInjector) : Trigger(injector) {
val data = JSONObject()
.put("time", time.value)
for (i in days.weekdays.indices) {
data.put(WeekDay.DayOfWeek.values()[i].name, days.weekdays[i])
data.put(WeekDay.DayOfWeek.entries[i].name, days.weekdays[i])
}
return data
}
@ -58,7 +58,7 @@ class TriggerRecurringTime(injector: HasAndroidInjector) : Trigger(injector) {
override fun fromJSON(data: String): Trigger {
val o = JSONObject(data)
for (i in days.weekdays.indices)
days.weekdays[i] = JsonHelper.safeGetBoolean(o, WeekDay.DayOfWeek.values()[i].name)
days.weekdays[i] = JsonHelper.safeGetBoolean(o, WeekDay.DayOfWeek.entries[i].name)
if (o.has("hour")) {
// do conversion from 2.5.1 format
val hour = JsonHelper.safeGetInt(o, "hour")
@ -90,7 +90,7 @@ class TriggerRecurringTime(injector: HasAndroidInjector) : Trigger(injector) {
override fun duplicate(): Trigger = TriggerRecurringTime(injector, this)
private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcPlusMinutes(minutesSinceMidnight)
private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcMidnightPlusMinutes(minutesSinceMidnight)
private fun getMinSinceMidnight(time: Long): Int = MidnightUtils.secondsFromMidnight(time) / 60

View file

@ -70,7 +70,7 @@ class TriggerTimeRange(injector: HasAndroidInjector) : Trigger(injector) {
override fun duplicate(): Trigger = TriggerTimeRange(injector, range.start, range.end)
private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcPlusMinutes(minutesSinceMidnight)
private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcMidnightPlusMinutes(minutesSinceMidnight)
private fun getMinSinceMidnight(time: Long): Int = MidnightUtils.secondsFromMidnight(time) / 60

View file

@ -16,7 +16,7 @@ class TriggerTimeRangeTest : TriggerTestBase() {
@BeforeEach
fun mock() {
now = 754 // in minutes from midnight
val nowMills = MidnightTime.calcPlusMinutes(now.toInt())
val nowMills = MidnightTime.calcMidnightPlusMinutes(now.toInt())
`when`(dateUtil.now()).thenReturn(nowMills)
`when`(rh.gs(R.string.timerange_value)).thenReturn("Time is between %1\$s and %2\$s")
}