diff --git a/server/src/domain/metadata/metadata.service.ts b/server/src/domain/metadata/metadata.service.ts index 22172221cd..7b1508bcef 100644 --- a/server/src/domain/metadata/metadata.service.ts +++ b/server/src/domain/metadata/metadata.service.ts @@ -157,9 +157,10 @@ export class MetadataService { await this.applyMotionPhotos(asset, tags); await this.applyReverseGeocoding(asset, exifData); await this.assetRepository.upsertExif(exifData); - let localDateTime = exifData.dateTimeOriginal ?? undefined; const dateTimeOriginal = exifDate(firstDateTime(tags as Tags)) ?? exifData.dateTimeOriginal; + let localDateTime = dateTimeOriginal ?? undefined; + const timeZoneOffset = tzOffset(firstDateTime(tags as Tags)) ?? 0; if (dateTimeOriginal && timeZoneOffset) { diff --git a/server/src/infra/entities/asset.entity.ts b/server/src/infra/entities/asset.entity.ts index cb01c63ab7..a2c9780ac8 100644 --- a/server/src/infra/entities/asset.entity.ts +++ b/server/src/infra/entities/asset.entity.ts @@ -84,7 +84,7 @@ export class AssetEntity { @Column({ type: 'timestamptz' }) fileCreatedAt!: Date; - @Column({ type: 'timestamp' }) + @Column({ type: 'timestamptz' }) localDateTime!: Date; @Column({ type: 'timestamptz' }) diff --git a/server/src/infra/migrations/1694525143117-AddLocalDateTime.ts b/server/src/infra/migrations/1694525143117-AddLocalDateTime.ts index 3acfdde370..dd24c07c0b 100644 --- a/server/src/infra/migrations/1694525143117-AddLocalDateTime.ts +++ b/server/src/infra/migrations/1694525143117-AddLocalDateTime.ts @@ -4,22 +4,15 @@ export class AddLocalDateTime1694525143117 implements MigrationInterface { name = 'AddLocalDateTime1694525143117'; public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "assets" ADD "localDateTime" TIMESTAMP`); - await queryRunner.query(` - update "assets" - set "localDateTime" = "fileCreatedAt"`); - - await queryRunner.query(` - update "assets" - set "localDateTime" = "fileCreatedAt" at TIME ZONE "exif"."timeZone" - from "exif" - where - "exif"."assetId" = "assets"."id" and - "exif"."timeZone" is not null`); - + await queryRunner.query(`ALTER TABLE "assets" ADD "localDateTime" TIMESTAMP WITH TIME ZONE`); + await queryRunner.query(`UPDATE "assets" SET "localDateTime" = "fileCreatedAt"`); await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "localDateTime" SET NOT NULL`); - await queryRunner.query(`CREATE INDEX "IDX_day_of_month" ON assets (EXTRACT(DAY FROM "localDateTime"))`); - await queryRunner.query(`CREATE INDEX "IDX_month" ON assets (EXTRACT(MONTH FROM "localDateTime"))`); + await queryRunner.query( + `CREATE INDEX "IDX_day_of_month" ON assets (EXTRACT(DAY FROM "localDateTime" AT TIME ZONE 'UTC'))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_month" ON assets (EXTRACT(MONTH FROM "localDateTime" AT TIME ZONE 'UTC'))`, + ); } public async down(queryRunner: QueryRunner): Promise { diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index 4e91f917c5..37153d86fb 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -29,6 +29,8 @@ const truncateMap: Record = { [TimeBucketSize.MONTH]: 'month', }; +const TIME_BUCKET_COLUMN = 'localDateTime'; + @Injectable() export class AssetRepository implements IAssetRepository { constructor( @@ -86,8 +88,8 @@ export class AssetRepository implements IAssetRepository { AND entity.isVisible = true AND entity.isArchived = false AND entity.resizePath IS NOT NULL - AND EXTRACT(DAY FROM entity.localDateTime) = :day - AND EXTRACT(MONTH FROM entity.localDateTime) = :month`, + AND EXTRACT(DAY FROM entity.localDateTime AT TIME ZONE 'UTC') = :day + AND EXTRACT(MONTH FROM entity.localDateTime AT TIME ZONE 'UTC') = :month`, { ownerId, day, @@ -480,19 +482,25 @@ export class AssetRepository implements IAssetRepository { return this.getBuilder(options) .select(`COUNT(asset.id)::int`, 'count') - .addSelect(`date_trunc('${truncateValue}', "fileCreatedAt")`, 'timeBucket') - .groupBy(`date_trunc('${truncateValue}', "fileCreatedAt")`) - .orderBy(`date_trunc('${truncateValue}', "fileCreatedAt")`, 'DESC') + .addSelect(`date_trunc('${truncateValue}', "${TIME_BUCKET_COLUMN}" at time zone 'UTC')`, 'timeBucket') + .groupBy(`date_trunc('${truncateValue}', "${TIME_BUCKET_COLUMN}" at time zone 'UTC')`) + .orderBy(`date_trunc('${truncateValue}', "${TIME_BUCKET_COLUMN}" at time zone 'UTC')`, 'DESC') .getRawMany(); } getByTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise { const truncateValue = truncateMap[options.size]; - return this.getBuilder(options) - .andWhere(`date_trunc('${truncateValue}', "fileCreatedAt") = :timeBucket`, { timeBucket }) - .orderBy(`date_trunc('day', "localDateTime")`, 'DESC') - .addOrderBy('asset.fileCreatedAt', 'DESC') - .getMany(); + return ( + this.getBuilder(options) + .andWhere(`date_trunc('${truncateValue}', "${TIME_BUCKET_COLUMN}" at time zone 'UTC') = :timeBucket`, { + timeBucket, + }) + // First sort by the day in localtime (put it in the right bucket) + .orderBy(`date_trunc('day', "${TIME_BUCKET_COLUMN}" at time zone 'UTC')`, 'DESC') + // and then sort by the actual time + .addOrderBy('asset.fileCreatedAt', 'DESC') + .getMany() + ); } private getBuilder(options: TimeBucketOptions) { diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index 06e3421d70..4cd0b2cec4 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -5,6 +5,7 @@ import { api } from '@api'; import { goto } from '$app/navigation'; import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; + import { fromLocalDateTime } from '$lib/utils/timeline-util'; import Play from 'svelte-material-icons/Play.svelte'; import Pause from 'svelte-material-icons/Pause.svelte'; import ChevronDown from 'svelte-material-icons/ChevronDown.svelte'; @@ -214,7 +215,7 @@

- {DateTime.fromISO(currentMemory.assets[0].localDateTime).toLocaleString(DateTime.DATE_FULL)} + {fromLocalDateTime(currentMemory.assets[0].localDateTime).toLocaleString(DateTime.DATE_FULL)}

{currentAsset.exifInfo?.city || ''} diff --git a/web/src/lib/components/photos-page/asset-date-group.svelte b/web/src/lib/components/photos-page/asset-date-group.svelte index b2c42cebd8..9207cd9248 100644 --- a/web/src/lib/components/photos-page/asset-date-group.svelte +++ b/web/src/lib/components/photos-page/asset-date-group.svelte @@ -1,10 +1,9 @@