From 2dd3a764ae80c0549394ea55d4854654d62defd1 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:17:16 +0530 Subject: [PATCH] fix: timezone in timeline bucketing (#25894) Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- .../entities/merged_asset.drift | 15 ++++++++----- .../entities/merged_asset.drift.dart | 2 +- .../repositories/timeline.repository.dart | 22 ++++++++++--------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/mobile/lib/infrastructure/entities/merged_asset.drift b/mobile/lib/infrastructure/entities/merged_asset.drift index 1db22b558e..217a18b75c 100644 --- a/mobile/lib/infrastructure/entities/merged_asset.drift +++ b/mobile/lib/infrastructure/entities/merged_asset.drift @@ -85,14 +85,14 @@ LIMIT $limit; mergedBucket(:group_by AS INTEGER): SELECT COUNT(*) as asset_count, - CASE - WHEN :group_by = 0 THEN STRFTIME('%Y-%m-%d', created_at, 'localtime') -- day - WHEN :group_by = 1 THEN STRFTIME('%Y-%m', created_at, 'localtime') -- month - END AS bucket_date + bucket_date FROM ( SELECT - rae.created_at + CASE + WHEN :group_by = 0 THEN STRFTIME('%Y-%m-%d', rae.local_date_time) + WHEN :group_by = 1 THEN STRFTIME('%Y-%m', rae.local_date_time) + END as bucket_date FROM remote_asset_entity rae LEFT JOIN @@ -107,7 +107,10 @@ FROM ) UNION ALL SELECT - lae.created_at + CASE + WHEN :group_by = 0 THEN STRFTIME('%Y-%m-%d', lae.created_at, 'localtime') + WHEN :group_by = 1 THEN STRFTIME('%Y-%m', lae.created_at, 'localtime') + END as bucket_date FROM local_asset_entity lae WHERE NOT EXISTS ( diff --git a/mobile/lib/infrastructure/entities/merged_asset.drift.dart b/mobile/lib/infrastructure/entities/merged_asset.drift.dart index f71aa8eb54..cd2fe5cfcc 100644 --- a/mobile/lib/infrastructure/entities/merged_asset.drift.dart +++ b/mobile/lib/infrastructure/entities/merged_asset.drift.dart @@ -79,7 +79,7 @@ class MergedAssetDrift extends i1.ModularAccessor { final expandeduserIds = $expandVar($arrayStartIndex, userIds.length); $arrayStartIndex += userIds.length; return customSelect( - 'SELECT COUNT(*) AS asset_count, CASE WHEN ?1 = 0 THEN STRFTIME(\'%Y-%m-%d\', created_at, \'localtime\') WHEN ?1 = 1 THEN STRFTIME(\'%Y-%m\', created_at, \'localtime\') END AS bucket_date FROM (SELECT rae.created_at FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT lae.created_at FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2)) GROUP BY bucket_date ORDER BY bucket_date DESC', + 'SELECT COUNT(*) AS asset_count, bucket_date FROM (SELECT CASE WHEN ?1 = 0 THEN STRFTIME(\'%Y-%m-%d\', rae.local_date_time) WHEN ?1 = 1 THEN STRFTIME(\'%Y-%m\', rae.local_date_time) END AS bucket_date FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT CASE WHEN ?1 = 0 THEN STRFTIME(\'%Y-%m-%d\', lae.created_at, \'localtime\') WHEN ?1 = 1 THEN STRFTIME(\'%Y-%m\', lae.created_at, \'localtime\') END AS bucket_date FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2)) GROUP BY bucket_date ORDER BY bucket_date DESC', variables: [ i0.Variable(groupBy), for (var $ in userIds) i0.Variable($), diff --git a/mobile/lib/infrastructure/repositories/timeline.repository.dart b/mobile/lib/infrastructure/repositories/timeline.repository.dart index b0548bdd28..0e145395df 100644 --- a/mobile/lib/infrastructure/repositories/timeline.repository.dart +++ b/mobile/lib/infrastructure/repositories/timeline.repository.dart @@ -126,7 +126,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { } final assetCountExp = _db.localAssetEntity.id.count(); - final dateExp = _db.localAssetEntity.createdAt.dateFmt(groupBy); + final dateExp = _db.localAssetEntity.createdAt.dateFmt(groupBy, toLocal: true); final query = _db.localAssetEntity.selectOnly().join([ @@ -203,7 +203,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { final album = albums.first; final isAscending = album.order == AlbumAssetOrder.asc; final assetCountExp = _db.remoteAssetEntity.id.count(); - final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy); + final dateExp = _db.remoteAssetEntity.localDateTime.dateFmt(groupBy); final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) @@ -280,7 +280,8 @@ class DriftTimelineRepository extends DriftDatabaseRepository { final sorted = List.from(assets)..sort((a, b) => b.createdAt.compareTo(a.createdAt)); final Map bucketCounts = {}; for (final asset in sorted) { - final date = DateTime(asset.createdAt.year, asset.createdAt.month, asset.createdAt.day); + final localTime = asset.createdAt.toLocal(); + final date = DateTime(localTime.year, localTime.month, localTime.day); bucketCounts[date] = (bucketCounts[date] ?? 0) + 1; } @@ -360,7 +361,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { } final assetCountExp = _db.remoteAssetEntity.id.count(); - final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy); + final dateExp = _db.remoteAssetEntity.localDateTime.dateFmt(groupBy); final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) @@ -430,7 +431,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { } final assetCountExp = _db.remoteAssetEntity.id.count(); - final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy); + final dateExp = _db.remoteAssetEntity.localDateTime.dateFmt(groupBy); final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) @@ -500,7 +501,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { } final assetCountExp = _db.remoteAssetEntity.id.count(); - final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy); + final dateExp = _db.remoteAssetEntity.localDateTime.dateFmt(groupBy); final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) @@ -602,7 +603,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository { } final assetCountExp = _db.remoteAssetEntity.id.count(); - final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy); + final dateExp = _db.remoteAssetEntity.localDateTime.dateFmt(groupBy); final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) @@ -664,10 +665,11 @@ List _generateBuckets(int count) { } extension on Expression { - Expression dateFmt(GroupAssetsBy groupBy) { + Expression dateFmt(GroupAssetsBy groupBy, {bool toLocal = false}) { // DateTimes are stored in UTC, so we need to convert them to local time inside the query before formatting - // to create the correct time bucket - final localTimeExp = modify(const DateTimeModifier.localTime()); + // to create the correct time bucket when toLocal is true + // toLocal is false for remote assets where localDateTime is already in the correct timezone + final localTimeExp = toLocal ? modify(const DateTimeModifier.localTime()) : this; return switch (groupBy) { GroupAssetsBy.day || GroupAssetsBy.auto => localTimeExp.date, GroupAssetsBy.month => localTimeExp.strftime("%Y-%m"),