mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
fix: time buckets (#4358)
* fix: time buckets * chore: update entity metadata * fix: set correct localDateTime * fix: display without timezone shifting * fix: handle non-utc databases * fix: scrollbar * docs: comment how buckets are sorted * chore: remove test/log * chore: lint --------- Co-authored-by: Jonathan Jogenfors <jonathan@jogenfors.se>
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -84,7 +84,7 @@ export class AssetEntity {
|
||||
@Column({ type: 'timestamptz' })
|
||||
fileCreatedAt!: Date;
|
||||
|
||||
@Column({ type: 'timestamp' })
|
||||
@Column({ type: 'timestamptz' })
|
||||
localDateTime!: Date;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
|
||||
@@ -4,22 +4,15 @@ export class AddLocalDateTime1694525143117 implements MigrationInterface {
|
||||
name = 'AddLocalDateTime1694525143117';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
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<void> {
|
||||
|
||||
@@ -29,6 +29,8 @@ const truncateMap: Record<TimeBucketSize, string> = {
|
||||
[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<AssetEntity[]> {
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user