Skip to content

Commit

Permalink
Count duration of entries spanning multiple days for each individual day
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiasn committed Jul 18, 2022
1 parent 3fc3a32 commit cce02f5
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 42 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
### Added:
- Weekly aggregation in story time charts
- Count duration for entries spanning multiple days for each individual day

## [0.8.112] - 2022-07-19
### Changed:
Expand Down
107 changes: 70 additions & 37 deletions lib/logic/charts/story_data.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'dart:core';
import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:lotti/classes/journal_entities.dart';
import 'package:lotti/widgets/charts/utils.dart';

Expand All @@ -9,68 +11,99 @@ enum AggregationTimeframe {
weekly,
}

List<MeasuredObservation> aggregateStoryDailyTimeSum(
List<JournalEntity?> entities, {
required DateTime rangeStart,
required DateTime rangeEnd,
}) {
final minutesByDay = <String, num>{};

List<String> daysInRange(DateTime rangeStart, DateTime rangeEnd) {
final range = rangeEnd.difference(rangeStart);
final dayStrings = List<String>.generate(range.inDays, (days) {
return List<String>.generate(range.inDays, (days) {
final day = rangeStart.add(Duration(days: days));
return ymd(day);
});
}

for (final dayString in dayStrings) {
minutesByDay[dayString] = 0;
}
List<String> daysInEntryRange(
DateTime? dateFrom,
DateTime? dateTo,
) {
final start = Jiffy(dateFrom).startOf(Units.DAY).dateTime;
final end = Jiffy(dateTo).endOf(Units.DAY).dateTime.add(
const Duration(days: 1),
);
return daysInRange(start, end);
}

for (final entity in entities) {
final dayString = ymd(entity!.meta.dateFrom);
final n = minutesByDay[dayString] ?? 0;
final duration =
entity.meta.dateTo.difference(entity.meta.dateFrom).inSeconds / 60;
minutesByDay[dayString] = n + duration;
DateTimeRange? overlappingRange(DateTimeRange a, DateTimeRange b) {
final start = DateTime.fromMillisecondsSinceEpoch(
max(a.start.millisecondsSinceEpoch, b.start.millisecondsSinceEpoch),
);
final end = DateTime.fromMillisecondsSinceEpoch(
min(a.end.millisecondsSinceEpoch, b.end.millisecondsSinceEpoch),
);

if (end.isBefore(start)) {
return null;
}

final aggregated = <MeasuredObservation>[];
for (final dayString in minutesByDay.keys) {
return DateTimeRange(start: start, end: end);
}

Map<String, num> durationsByDayInRange(
DateTime? dateFrom,
DateTime? dateTo,
) {
final minutesByDay = <String, num>{};

if (dateFrom == null || dateTo == null) {
return {};
}
for (final dayString in daysInEntryRange(dateFrom, dateTo)) {
final day = DateTime.parse(dayString);
aggregated.add(MeasuredObservation(day, minutesByDay[dayString] ?? 0));

final byDay = overlappingRange(
DateTimeRange(
start: dateFrom,
end: dateTo,
),
DateTimeRange(
start: day,
end: day.add(const Duration(days: 1)),
),
);

if (byDay != null) {
final n = minutesByDay[dayString] ?? 0;
minutesByDay[dayString] = n + byDay.duration.inSeconds / 60;
}
}

return aggregated;
return minutesByDay;
}

List<MeasuredObservation> aggregateStoryWeeklyTimeSum(
List<MeasuredObservation> aggregateStoryDailyTimeSum(
List<JournalEntity?> entities, {
required DateTime rangeStart,
required DateTime rangeEnd,
}) {
final minutesByDay = <String, num>{};

final range = rangeEnd.difference(rangeStart);
final dayStrings = List<String>.generate(range.inDays, (days) {
final day = rangeStart.add(Duration(days: days));
return ymd(day);
});
final dayStrings = daysInRange(rangeStart, rangeEnd);
final days = dayStrings.toSet();

for (final dayString in dayStrings) {
minutesByDay[dayString] = 0;
}

for (final entity in entities) {
debugPrint('aggregateStoryWeeklyTimeSum ${entity?.meta.dateFrom}');

final dayString = ymd(entity!.meta.dateFrom);
final n = minutesByDay[dayString] ?? 0;
final duration =
entity.meta.dateTo.difference(entity.meta.dateFrom).inSeconds / 60;
minutesByDay[dayString] = n + duration;
durationsByDayInRange(
entity?.meta.dateFrom,
entity?.meta.dateTo,
).forEach((dayString, minutes) {
if (days.contains(dayString)) {
final n = minutesByDay[dayString] ?? 0;
minutesByDay[dayString] = n + minutes;
}
});
}

final aggregated = <MeasuredObservation>[];

for (final dayString in minutesByDay.keys) {
final day = DateTime.parse(dayString);
aggregated.add(MeasuredObservation(day, minutesByDay[dayString] ?? 0));
Expand All @@ -93,7 +126,7 @@ List<MeasuredObservation> aggregateStoryTimeSum(
rangeEnd: rangeEnd,
);
case AggregationTimeframe.weekly:
return aggregateStoryWeeklyTimeSum(
return aggregateStoryDailyTimeSum(
entities,
rangeStart: rangeStart,
rangeEnd: rangeEnd,
Expand Down
7 changes: 7 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
jiffy:
dependency: "direct main"
description:
name: jiffy
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
js:
dependency: transitive
description:
Expand Down
3 changes: 2 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: lotti
description: A Smart Journal.
publish_to: 'none'
version: 0.8.113+1167
version: 0.8.113+1168

msix_config:
display_name: Lotti
Expand Down Expand Up @@ -98,6 +98,7 @@ dependencies:

hotkey_manager: ^0.1.6
intl: ^0.17.0
jiffy: ^5.0.0
json_annotation: ^4.4.0
just_audio: ^0.9.12
keyboard_dismisser: ^3.0.0
Expand Down
2 changes: 1 addition & 1 deletion test/journal_test_data/test_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,6 @@ final testDurationEntry5 = JournalEntry(
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
dateFrom: DateTime(2022, 7, 2, 22),
dateTo: DateTime(2022, 7, 3, 1),
dateTo: DateTime(2022, 7, 5, 1),
),
);
86 changes: 84 additions & 2 deletions test/logic/charts/story_data_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,86 @@ import '../../journal_test_data/test_data.dart';

void main() {
group('Story data tests - ', () {
test(
'daysInRange for range for week',
() {
expect(
daysInRange(DateTime(2022, 7, 1), DateTime(2022, 7, 7)),
[
'2022-07-01',
'2022-07-02',
'2022-07-03',
'2022-07-04',
'2022-07-05',
'2022-07-06',
],
);
},
);

test(
'daysInRange for single day',
() {
expect(
daysInRange(DateTime(2022, 7, 1), DateTime(2022, 7, 2, 1)),
[
'2022-07-01',
],
);
},
);

test(
'daysInRange for longer entry',
() {
expect(
daysInEntryRange(
testDurationEntry5.meta.dateFrom,
testDurationEntry5.meta.dateTo,
),
[
'2022-07-02',
'2022-07-03',
'2022-07-04',
'2022-07-05',
],
);
},
);

test(
'durationsByDayInRange for short entry',
() {
expect(
durationsByDayInRange(
testDurationEntry1.meta.dateFrom,
testDurationEntry1.meta.dateTo,
),
{
'2022-07-03': 60.0,
},
);
},
);

test(
'durationsByDayInRange for longer entry',
() {
expect(
durationsByDayInRange(
testDurationEntry5.meta.dateFrom,
testDurationEntry5.meta.dateTo,
),
{
'2022-07-02': 120.0,
'2022-07-03': 1440.0,
'2022-07-04': 1440.0,
'2022-07-05': 60.0
},
);
},
);

test(
'daily aggregates in range created for empty data',
() {
Expand Down Expand Up @@ -63,13 +143,15 @@ void main() {
testDurationEntry5,
],
rangeStart: DateTime(2022, 7, 1),
rangeEnd: DateTime(2022, 7, 4),
rangeEnd: DateTime(2022, 7, 6),
timeframe: AggregationTimeframe.daily,
),
[
MeasuredObservation(DateTime(2022, 7, 1), 60.0),
MeasuredObservation(DateTime(2022, 7, 2), 240.0),
MeasuredObservation(DateTime(2022, 7, 3), 60.0),
MeasuredObservation(DateTime(2022, 7, 3), 1440.0),
MeasuredObservation(DateTime(2022, 7, 4), 1440.0),
MeasuredObservation(DateTime(2022, 7, 5), 60.0),
],
);
},
Expand Down

0 comments on commit cce02f5

Please sign in to comment.