diff --git a/lib/model/entry/extensions/props.dart b/lib/model/entry/extensions/props.dart index 8953e04c7..1b231ea59 100644 --- a/lib/model/entry/extensions/props.dart +++ b/lib/model/entry/extensions/props.dart @@ -10,6 +10,7 @@ import 'package:aves/ref/unicode.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/text.dart'; import 'package:aves/utils/android_file_utils.dart'; +import 'package:aves/utils/time_utils.dart'; extension ExtraAvesEntryProps on AvesEntry { bool get isValid => !isMissingAtPath && sizeBytes != 0 && width > 0 && height > 0; @@ -85,7 +86,7 @@ extension ExtraAvesEntryProps on AvesEntry { int? get trashDaysLeft { final dateMillis = trashDetails?.dateMillis; if (dateMillis == null) return null; - return DateTime.fromMillisecondsSinceEpoch(dateMillis).add(TrashMixin.binKeepDuration).difference(DateTime.now()).inDays; + return DateTime.fromMillisecondsSinceEpoch(dateMillis).add(TrashMixin.binKeepDuration).difference(DateTime.now()).inHumanDays; } // storage diff --git a/lib/utils/time_utils.dart b/lib/utils/time_utils.dart index cabb53787..1b877a4ef 100644 --- a/lib/utils/time_utils.dart +++ b/lib/utils/time_utils.dart @@ -26,6 +26,12 @@ extension ExtraDateTime on DateTime { DateTime addDays(int days) => DateTime(year, month, day + days, hour, minute, second, millisecond, microsecond); } +extension ExtraDuration on Duration { + // using `Duration.inDays` may yield surprising results when crossing DST boundaries + // because a day will not have 24 hours, so we use the following approximation instead + int get inHumanDays => (inMicroseconds / Duration.microsecondsPerDay).round(); +} + final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); // Overflowing timestamps that are supposed to be in milliseconds diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 6a1fcf042..02c2c11f5 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -14,6 +14,7 @@ import 'package:aves/model/source/section_keys.dart'; import 'package:aves/ref/mime_types.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; +import 'package:aves/utils/time_utils.dart'; import 'package:aves/widgets/collection/app_bar.dart'; import 'package:aves/widgets/collection/draggable_thumb_label.dart'; import 'package:aves/widgets/collection/grid/list_details_theme.dart'; @@ -673,7 +674,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge final oldest = lastKey.date; if (newest != null && oldest != null) { final locale = context.l10n.localeName; - final dateFormat = (newest.difference(oldest).inDays).abs() > 365 ? DateFormat.y(locale) : DateFormat.MMM(locale); + final dateFormat = (newest.difference(oldest).inHumanDays).abs() > 365 ? DateFormat.y(locale) : DateFormat.MMM(locale); String? lastLabel; sectionLayouts.forEach((section) { final date = (section.sectionKey as EntryDateSectionKey).date; diff --git a/lib/widgets/stats/date/axis.dart b/lib/widgets/stats/date/axis.dart index af043e92b..01bca2095 100644 --- a/lib/widgets/stats/date/axis.dart +++ b/lib/widgets/stats/date/axis.dart @@ -34,7 +34,7 @@ class TimeAxisSpec { first = first.date; last = last.date; - final rangeDays = last.difference(first).inDays; + final rangeDays = last.difference(first).inHumanDays; final delta = max(1, (rangeDays / 5).ceil()); List> ticks = []; diff --git a/lib/widgets/stats/date/histogram.dart b/lib/widgets/stats/date/histogram.dart index 0debd691b..4147dda96 100644 --- a/lib/widgets/stats/date/histogram.dart +++ b/lib/widgets/stats/date/histogram.dart @@ -5,6 +5,7 @@ import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/sort.dart'; import 'package:aves/model/filters/date.dart'; import 'package:aves/theme/durations.dart'; +import 'package:aves/utils/time_utils.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/fx/transitions.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; @@ -56,7 +57,7 @@ class _HistogramState extends State with AutomaticKeepAliveClientMixi var firstDate = entriesByDateDescending.lastWhereOrNull((entry) => entry.bestDate != null)?.bestDate; if (lastDate != null && firstDate != null) { - final rangeDays = lastDate.difference(firstDate).inDays; + final rangeDays = lastDate.difference(firstDate).inHumanDays; if (rangeDays > 1) { if (rangeDays <= 31) { _level = DateLevel.ymd; @@ -113,10 +114,10 @@ class _HistogramState extends State with AutomaticKeepAliveClientMixi late DateTime Function(DateTime date) incrementDate; switch (level) { case DateLevel.ymd: - xCount = xRange.inDays; + xCount = xRange.inHumanDays; incrementDate = (date) => DateTime(date.year, date.month, date.day + 1); case DateLevel.ym: - xCount = (xRange.inDays / 30.5).round(); + xCount = (xRange.inHumanDays / 30.5).round(); incrementDate = (date) => DateTime(date.year, date.month + 1); default: xCount = lastDate.year - firstDate.year;