implement average aggregation

pull/2121/head
DasCapschen 6 months ago
parent 107c898f51
commit efd82cc001

@ -44,6 +44,7 @@ import org.isoron.uhabits.activities.common.dialogs.WeekdayPickerDialog
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateHabitCommand
import org.isoron.uhabits.core.commands.EditHabitCommand
import org.isoron.uhabits.core.models.AggregationType
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitType
@ -85,6 +86,7 @@ class EditHabitActivity : AppCompatActivity() {
var reminderMin = -1
var reminderDays: WeekdayList = WeekdayList.EVERY_DAY
var targetType = NumericalHabitType.AT_LEAST
var aggregationType = AggregationType.SUM
override fun onCreate(state: Bundle?) {
super.onCreate(state)
@ -107,6 +109,7 @@ class EditHabitActivity : AppCompatActivity() {
freqNum = habit.frequency.numerator
freqDen = habit.frequency.denominator
targetType = habit.targetType
aggregationType = habit.aggregationType
habit.reminder?.let {
reminderHour = it.hour
reminderMin = it.minute
@ -191,6 +194,24 @@ class EditHabitActivity : AppCompatActivity() {
dialog.dismissCurrentAndShow()
}
populateAggregationType()
binding.aggregationTypePicker.setOnClickListener {
val builder = AlertDialog.Builder(this)
val arrayAdapter = ArrayAdapter<String>(this, android.R.layout.select_dialog_item)
arrayAdapter.add(getString(R.string.aggregation_type_sum))
arrayAdapter.add(getString(R.string.aggregation_type_average))
builder.setAdapter(arrayAdapter) { dialog, which ->
aggregationType = when (which) {
0 -> AggregationType.SUM
else -> AggregationType.AVERAGE
}
populateAggregationType()
dialog.dismiss()
}
val dialog = builder.create()
dialog.dismissCurrentAndShow()
}
binding.numericalFrequencyPicker.setOnClickListener {
val builder = AlertDialog.Builder(this)
val arrayAdapter = ArrayAdapter<String>(this, android.R.layout.select_dialog_item)
@ -282,6 +303,7 @@ class EditHabitActivity : AppCompatActivity() {
if (habitType == HabitType.NUMERICAL) {
habit.targetValue = binding.targetInput.text.toString().toDouble()
habit.targetType = targetType
habit.aggregationType = aggregationType
habit.unit = binding.unitInput.text.trim().toString()
}
habit.type = habitType
@ -350,6 +372,13 @@ class EditHabitActivity : AppCompatActivity() {
}
}
private fun populateAggregationType() {
binding.aggregationTypePicker.text = when(aggregationType) {
AggregationType.SUM -> getString(R.string.aggregation_type_sum)
AggregationType.AVERAGE -> getString(R.string.aggregation_type_average)
}
}
private fun updateColors() {
androidColor = themeSwitcher.currentTheme.color(color).toInt()
binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor)

@ -211,20 +211,51 @@
</FrameLayout>
</LinearLayout>
<FrameLayout
<LinearLayout
android:id="@+id/targetTypeOuterBox"
style="@style/FormOuterBox">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/target_type" />
<TextView
style="@style/FormDropdown"
android:id="@+id/targetTypePicker"
android:textColor="?attr/contrast100"
/>
</LinearLayout>
</FrameLayout>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<FrameLayout
style="@style/FormOuterBox"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/target_type" />
<TextView
android:id="@+id/targetTypePicker"
style="@style/FormDropdown"
android:textColor="?attr/contrast100" />
</LinearLayout>
</FrameLayout>
<FrameLayout
style="@style/FormOuterBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/aggregation_type" />
<TextView
android:id="@+id/aggregationTypePicker"
style="@style/FormDropdown"
android:textColor="?attr/contrast100" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
<!-- Reminder -->

@ -218,4 +218,7 @@
<string name="activity_not_found">Für diese Aktion wurde keine App gefunden.</string>
<string name="pref_midnight_delay_title">Verlängere den Tag um ein paar Stunden nach Mitternacht</string>
<string name="pref_midnight_delay_description">Bis 3:00 Uhr warten, bevor ein neuer Tag angezeigt wird. Nützlich, wenn du normalerweise nach Mitternacht schlafen gehst. Benötigt einen Neustart der App.</string>
<string name="aggregation_type">Aggregation</string>
<string name="aggregation_type_sum">Summe</string>
<string name="aggregation_type_average">Mittelwert</string>
</resources>

@ -233,4 +233,7 @@
<string name="activity_not_found">No app was found to support this action</string>
<string name="pref_midnight_delay_title">Extend day a few hours past midnight</string>
<string name="pref_midnight_delay_description">Wait until 3:00 AM to show a new day. Useful if you typically go to sleep after midnight. Requires app restart.</string>
<string name="aggregation_type">Aggregation</string>
<string name="aggregation_type_sum">Sum</string>
<string name="aggregation_type_average">Average</string>
</resources>

@ -20,4 +20,4 @@ package org.isoron.uhabits.core
const val DATABASE_FILENAME = "uhabits.db"
const val DATABASE_VERSION = 25
const val DATABASE_VERSION = 26

@ -0,0 +1,15 @@
package org.isoron.uhabits.core.models
enum class AggregationType(val value: Int) {
SUM(0), AVERAGE(1);
companion object {
fun fromInt(value: Int): AggregationType {
return when (value) {
SUM.value -> SUM
AVERAGE.value -> AVERAGE
else -> throw IllegalStateException()
}
}
}
}

@ -30,6 +30,7 @@ import javax.annotation.concurrent.ThreadSafe
import kotlin.collections.set
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
@ThreadSafe
open class EntryList {
@ -285,20 +286,28 @@ open class EntryList {
*
* SKIP values are converted to zero (if they weren't, each SKIP day would count as 0.003).
*
* If average aggregation is used, we do not convert any values. Instead we filter out special
* values like SKIP and UNKNOWN, because they should not contribute to the average calculated,
* but if the user explicitly enters a 0, it SHOULD count towards the average.
* Because we filter out entries, we must also be careful not to divide by zero.
*
* The returned list is sorted by timestamp, with the newest entry coming first and the oldest entry
* coming last. If the original list has gaps in it (for example, weeks or months without any
* entries), then the list produced by this method will also have gaps.
*
* The argument [firstWeekday] is only relevant when truncating by week.
*/
fun List<Entry>.groupedSum(
fun List<Entry>.groupedAggregate(
truncateField: DateUtils.TruncateField,
firstWeekday: Int = Calendar.SATURDAY,
isNumerical: Boolean
isNumerical: Boolean,
aggregationType: AggregationType
): List<Entry> {
return this.map { (timestamp, value) ->
if (isNumerical) {
if (value == SKIP) {
if (aggregationType == AggregationType.AVERAGE) {
Entry(timestamp, value)
} else if (value == SKIP) {
Entry(timestamp, 0)
} else {
Entry(timestamp, max(0, value))
@ -312,7 +321,17 @@ fun List<Entry>.groupedSum(
firstWeekday
)
}.entries.map { (timestamp, entries) ->
Entry(timestamp, entries.sumOf { it.value })
if (isNumerical && aggregationType == AggregationType.AVERAGE) {
val filteredEntries = entries.filter { it.value == 0 || it.value >= 1000 }
if (filteredEntries.size == 0) {
Entry(timestamp, 0)
} else {
val value = filteredEntries.sumOf { it.value }.toFloat() / filteredEntries.size
Entry(timestamp, value.roundToInt() )
}
} else {
Entry(timestamp, entries.sumOf { it.value })
}
}.sortedBy { (timestamp, _) ->
-timestamp.unixTime
}

@ -22,6 +22,7 @@ import org.isoron.uhabits.core.utils.DateUtils
import java.util.UUID
data class Habit(
var aggregationType: AggregationType = AggregationType.SUM,
var color: PaletteColor = PaletteColor(8),
var description: String = "",
var frequency: Frequency = Frequency.DAILY,
@ -108,6 +109,7 @@ data class Habit(
}
fun copyFrom(other: Habit) {
this.aggregationType = other.aggregationType
this.color = other.color
this.description = other.description
this.frequency = other.frequency
@ -128,6 +130,7 @@ data class Habit(
if (this === other) return true
if (other !is Habit) return false
if (aggregationType != other.aggregationType) return false
if (color != other.color) return false
if (description != other.description) return false
if (frequency != other.frequency) return false
@ -148,6 +151,7 @@ data class Habit(
override fun hashCode(): Int {
var result = color.hashCode()
result = 31 * result + aggregationType.value
result = 31 * result + description.hashCode()
result = 31 * result + frequency.hashCode()
result = 31 * result + (id?.hashCode() ?: 0)

@ -20,6 +20,7 @@ package org.isoron.uhabits.core.models.sqlite.records
import org.isoron.uhabits.core.database.Column
import org.isoron.uhabits.core.database.Table
import org.isoron.uhabits.core.models.AggregationType
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitType
@ -49,6 +50,9 @@ class HabitRecord {
@field:Column(name = "freq_den")
var freqDen: Int? = null
@field:Column(name = "aggregation_type")
var aggregationType: Int? = null
@field:Column
var color: Int? = null
@ -93,6 +97,7 @@ class HabitRecord {
name = model.name
description = model.description
highlight = 0
aggregationType = model.aggregationType.value
color = model.color.paletteIndex
archived = if (model.isArchived) 1 else 0
type = model.type.value
@ -122,6 +127,7 @@ class HabitRecord {
habit.description = description!!
habit.question = question!!
habit.frequency = Frequency(freqNum!!, freqDen!!)
habit.aggregationType = AggregationType.fromInt(aggregationType!!)
habit.color = PaletteColor(color!!)
habit.isArchived = archived != 0
habit.type = HabitType.fromInt(type!!)

@ -56,7 +56,7 @@ class ListHabitsSelectionMenuBehavior @Inject constructor(
}
fun onChangeColor() {
val (color) = adapter.getSelected()[0]
val (_, color) = adapter.getSelected()[0]
screen.showColorPicker(color) { selectedColor: PaletteColor ->
commandRunner.run(ChangeHabitColorCommand(habitList, adapter.getSelected(), selectedColor))
adapter.clearSelection()

@ -22,7 +22,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.groupedSum
import org.isoron.uhabits.core.models.groupedAggregate
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils
@ -59,10 +59,11 @@ class BarCardPresenter(
}
val today = DateUtils.getTodayWithOffset()
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
val entries = habit.computedEntries.getByInterval(oldest, today).groupedSum(
val entries = habit.computedEntries.getByInterval(oldest, today).groupedAggregate(
truncateField = ScoreCardPresenter.getTruncateField(bucketSize),
firstWeekday = firstWeekday,
isNumerical = habit.isNumerical
isNumerical = habit.isNumerical,
aggregationType = habit.aggregationType,
)
return BarCardState(
theme = theme,

@ -19,10 +19,12 @@
package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.AggregationType
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.countSkippedDays
import org.isoron.uhabits.core.models.groupedSum
import org.isoron.uhabits.core.models.groupedAggregate
import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils
import java.util.ArrayList
@ -48,19 +50,72 @@ class TargetCardPresenter {
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
val entries = habit.computedEntries.getByInterval(oldest, today)
val valueToday = entries.groupedSum(
val valueToday = entries.groupedAggregate(
truncateField = DateUtils.TruncateField.DAY,
isNumerical = habit.isNumerical
isNumerical = habit.isNumerical,
aggregationType = habit.aggregationType
).firstOrNull()?.value ?: 0
val skippedDayToday = entries.countSkippedDays(
truncateField = DateUtils.TruncateField.DAY
).firstOrNull()?.value ?: 0
val valueThisWeek = entries.groupedSum(
val valueThisWeek = entries.groupedAggregate(
truncateField = DateUtils.TruncateField.WEEK_NUMBER,
firstWeekday = firstWeekday,
isNumerical = habit.isNumerical
isNumerical = habit.isNumerical,
aggregationType = habit.aggregationType
).firstOrNull()?.value ?: 0
val valueThisMonth = entries.groupedAggregate(
truncateField = DateUtils.TruncateField.MONTH,
isNumerical = habit.isNumerical,
aggregationType = habit.aggregationType
).firstOrNull()?.value ?: 0
val valueThisQuarter = entries.groupedAggregate(
truncateField = DateUtils.TruncateField.QUARTER,
isNumerical = habit.isNumerical,
aggregationType = habit.aggregationType
).firstOrNull()?.value ?: 0
val valueThisYear = entries.groupedAggregate(
truncateField = DateUtils.TruncateField.YEAR,
isNumerical = habit.isNumerical,
aggregationType = habit.aggregationType
).firstOrNull()?.value ?: 0
val values = ArrayList<Double>()
if (habit.frequency.denominator <= 1) values.add(valueToday / 1e3)
if (habit.frequency.denominator <= 7) values.add(valueThisWeek / 1e3)
values.add(valueThisMonth / 1e3)
values.add(valueThisQuarter / 1e3)
values.add(valueThisYear / 1e3)
val targets = when(habit.aggregationType) {
AggregationType.SUM -> getTargetsSum(habit, firstWeekday, entries)
AggregationType.AVERAGE -> getTargetsAvg(habit)
}
val intervals = ArrayList<Int>()
if (habit.frequency.denominator <= 1) intervals.add(1)
if (habit.frequency.denominator <= 7) intervals.add(7)
intervals.add(30)
intervals.add(91)
intervals.add(365)
return TargetCardState(
color = habit.color,
values = values,
targets = targets,
intervals = intervals,
theme = theme
)
}
private fun getTargetsSum(
habit: Habit,
firstWeekday: Int,
entries: List<Entry>
): ArrayList<Double> {
val skippedDayToday = entries.countSkippedDays(
truncateField = DateUtils.TruncateField.DAY
).firstOrNull()?.value ?: 0
val skippedDaysThisWeek = entries.countSkippedDays(
@ -68,29 +123,14 @@ class TargetCardPresenter {
firstWeekday = firstWeekday
).firstOrNull()?.value ?: 0
val valueThisMonth = entries.groupedSum(
truncateField = DateUtils.TruncateField.MONTH,
isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0
val skippedDaysThisMonth = entries.countSkippedDays(
truncateField = DateUtils.TruncateField.MONTH
).firstOrNull()?.value ?: 0
val valueThisQuarter = entries.groupedSum(
truncateField = DateUtils.TruncateField.QUARTER,
isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0
val skippedDaysThisQuarter = entries.countSkippedDays(
truncateField = DateUtils.TruncateField.QUARTER
).firstOrNull()?.value ?: 0
val valueThisYear = entries.groupedSum(
truncateField = DateUtils.TruncateField.YEAR,
isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0
val skippedDaysThisYear = entries.countSkippedDays(
truncateField = DateUtils.TruncateField.YEAR
).firstOrNull()?.value ?: 0
@ -136,13 +176,6 @@ class TargetCardPresenter {
targetThisQuarter = max(0.0, targetThisQuarter - dailyTarget * skippedDaysThisQuarter)
targetThisYear = max(0.0, targetThisYear - dailyTarget * skippedDaysThisYear)
val values = ArrayList<Double>()
if (habit.frequency.denominator <= 1) values.add(valueToday / 1e3)
if (habit.frequency.denominator <= 7) values.add(valueThisWeek / 1e3)
values.add(valueThisMonth / 1e3)
values.add(valueThisQuarter / 1e3)
values.add(valueThisYear / 1e3)
val targets = ArrayList<Double>()
if (habit.frequency.denominator <= 1) targets.add(targetToday)
if (habit.frequency.denominator <= 7) targets.add(targetThisWeek)
@ -150,20 +183,17 @@ class TargetCardPresenter {
targets.add(targetThisQuarter)
targets.add(targetThisYear)
val intervals = ArrayList<Int>()
if (habit.frequency.denominator <= 1) intervals.add(1)
if (habit.frequency.denominator <= 7) intervals.add(7)
intervals.add(30)
intervals.add(91)
intervals.add(365)
return targets
}
return TargetCardState(
color = habit.color,
values = values,
targets = targets,
intervals = intervals,
theme = theme
)
private fun getTargetsAvg(habit: Habit): ArrayList<Double> {
val targets = ArrayList<Double>()
if (habit.frequency.denominator <= 1) targets.add(habit.targetValue)
if (habit.frequency.denominator <= 7) targets.add(habit.targetValue)
targets.add(habit.targetValue)
targets.add(habit.targetValue)
targets.add(habit.targetValue)
return targets
}
}
}

@ -0,0 +1 @@
alter table Habits add column aggregation_type integer not null default 0;

@ -142,33 +142,93 @@ class EntryListTest {
entries.add(Entry(reference.minus(offsets[it]), values[it]))
}
val byMonth = entries.getKnown().groupedSum(
val byMonth = entries.getKnown().groupedAggregate(
truncateField = DateUtils.TruncateField.MONTH,
isNumerical = true
isNumerical = true,
aggregationType = AggregationType.SUM
)
assertThat(byMonth.size, equalTo(17))
assertThat(byMonth[0], equalTo(Entry(Timestamp.from(2014, Calendar.JUNE, 1), 230)))
assertThat(byMonth[6], equalTo(Entry(Timestamp.from(2013, Calendar.DECEMBER, 1), 1988)))
assertThat(byMonth[12], equalTo(Entry(Timestamp.from(2013, Calendar.MAY, 1), 1271)))
val byQuarter = entries.getKnown().groupedSum(
val byQuarter = entries.getKnown().groupedAggregate(
truncateField = DateUtils.TruncateField.QUARTER,
isNumerical = true
isNumerical = true,
aggregationType = AggregationType.SUM
)
assertThat(byQuarter.size, equalTo(6))
assertThat(byQuarter[0], equalTo(Entry(Timestamp.from(2014, Calendar.APRIL, 1), 3263)))
assertThat(byQuarter[3], equalTo(Entry(Timestamp.from(2013, Calendar.JULY, 1), 3838)))
assertThat(byQuarter[5], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 4975)))
val byYear = entries.getKnown().groupedSum(
val byYear = entries.getKnown().groupedAggregate(
truncateField = DateUtils.TruncateField.YEAR,
isNumerical = true
isNumerical = true,
aggregationType = AggregationType.SUM
)
assertThat(byYear.size, equalTo(2))
assertThat(byYear[0], equalTo(Entry(Timestamp.from(2014, Calendar.JANUARY, 1), 8227)))
assertThat(byYear[1], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 16172)))
}
@Test
fun testGroupByNumericalAverage() {
val offsets = intArrayOf(
0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78,
83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164,
166, 171, 173, 176, 179, 183, 191, 259, 264, 268, 270, 275, 282, 284, 289, 295,
302, 306, 310, 315, 323, 325, 328, 335, 343, 349, 351, 353, 357, 359, 360, 367,
372, 376, 380, 385, 393, 400, 404, 412, 415, 418, 422, 425, 433, 437, 444, 449,
455, 460, 462, 465, 470, 471, 479, 481, 485, 489, 494, 495, 500, 501, 503, 507
)
val values = intArrayOf(
230, 306, 148, 281, 134, 285, 104, 158, 325, 236, 303, 210, 118, 124,
301, 201, 156, 376, 347, 367, 396, 134, 160, 381, 155, 354, 231, 134, 164, 354,
236, 398, 199, 221, 208, 397, 253, 276, 214, 341, 299, 221, 353, 250, 341, 168,
374, 205, 182, 217, 297, 321, 104, 237, 294, 110, 136, 229, 102, 271, 250, 294,
158, 319, 379, 126, 282, 155, 288, 159, 215, 247, 207, 226, 244, 158, 371, 219,
272, 228, 350, 153, 356, 279, 394, 202, 213, 214, 112, 248, 139, 245, 165, 256,
370, 187, 208, 231, 341, 312
)
val reference = Timestamp.from(2014, Calendar.JUNE, 1)
val entries = EntryList()
offsets.indices.forEach {
entries.add(Entry(reference.minus(offsets[it]), values[it]))
}
val byMonthAvg = entries.getKnown().groupedAggregate(
truncateField = DateUtils.TruncateField.MONTH,
isNumerical = true,
aggregationType = AggregationType.AVERAGE
)
assertThat(byMonthAvg.size, equalTo(17))
assertThat(byMonthAvg[0], equalTo(Entry(Timestamp.from(2014, Calendar.JUNE, 1), 230)))
assertThat(byMonthAvg[6], equalTo(Entry(Timestamp.from(2013, Calendar.DECEMBER, 1), 284)))
assertThat(byMonthAvg[12], equalTo(Entry(Timestamp.from(2013, Calendar.MAY, 1), 212)))
val byQuarterAvg = entries.getKnown().groupedAggregate(
truncateField = DateUtils.TruncateField.QUARTER,
isNumerical = true,
aggregationType = AggregationType.AVERAGE
)
assertThat(byQuarterAvg.size, equalTo(6))
assertThat(byQuarterAvg[0], equalTo(Entry(Timestamp.from(2014, Calendar.APRIL, 1), 218)))
assertThat(byQuarterAvg[3], equalTo(Entry(Timestamp.from(2013, Calendar.JULY, 1), 226)))
assertThat(byQuarterAvg[5], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 249)))
val byYearAvg = entries.getKnown().groupedAggregate(
truncateField = DateUtils.TruncateField.YEAR,
isNumerical = true,
aggregationType = AggregationType.AVERAGE
)
assertThat(byYearAvg.size, equalTo(2))
assertThat(byYearAvg[0], equalTo(Entry(Timestamp.from(2014, Calendar.JANUARY, 1), 242)))
assertThat(byYearAvg[1], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 245)))
}
@Test
fun testGroupByBoolean() {
val offsets = intArrayOf(
@ -186,27 +246,30 @@ class EntryListTest {
entries.add(Entry(reference.minus(offsets[it]), YES_MANUAL))
}
val byMonth = entries.getKnown().groupedSum(
val byMonth = entries.getKnown().groupedAggregate(
truncateField = DateUtils.TruncateField.MONTH,
isNumerical = false
isNumerical = false,
aggregationType = AggregationType.SUM
)
assertThat(byMonth.size, equalTo(17))
assertThat(byMonth[0], equalTo(Entry(Timestamp.from(2014, Calendar.JUNE, 1), 1_000)))
assertThat(byMonth[6], equalTo(Entry(Timestamp.from(2013, Calendar.DECEMBER, 1), 7_000)))
assertThat(byMonth[12], equalTo(Entry(Timestamp.from(2013, Calendar.MAY, 1), 6_000)))
val byQuarter = entries.getKnown().groupedSum(
val byQuarter = entries.getKnown().groupedAggregate(
truncateField = DateUtils.TruncateField.QUARTER,
isNumerical = false
isNumerical = false,
aggregationType = AggregationType.SUM
)
assertThat(byQuarter.size, equalTo(6))
assertThat(byQuarter[0], equalTo(Entry(Timestamp.from(2014, Calendar.APRIL, 1), 15_000)))
assertThat(byQuarter[3], equalTo(Entry(Timestamp.from(2013, Calendar.JULY, 1), 17_000)))
assertThat(byQuarter[5], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 20_000)))
val byYear = entries.getKnown().groupedSum(
val byYear = entries.getKnown().groupedAggregate(
truncateField = DateUtils.TruncateField.YEAR,
isNumerical = false
isNumerical = false,
aggregationType = AggregationType.SUM
)
assertThat(byYear.size, equalTo(2))
assertThat(byYear[0], equalTo(Entry(Timestamp.from(2014, Calendar.JANUARY, 1), 34_000)))

Loading…
Cancel
Save