calculated day's difference over DST correctly
This commit is contained in:
parent
a8fc73c8ad
commit
47758b6b27
11 changed files with 117 additions and 79 deletions
|
@ -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()
|
||||
}
|
|
@ -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:")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -331,18 +331,21 @@ 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) {
|
||||
jsonSettings.put("curve", "ultra-rapid")
|
||||
jsonSettings.put("useCustomPeakTime", true)
|
||||
jsonSettings.put("insulinPeakTime", peakTime)
|
||||
} else if (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)
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -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()))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue