From cea2c055a7183a29ddf0b700b2753474973de2d3 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Tue, 8 Oct 2024 10:40:48 -0400 Subject: [PATCH] fix(server): timezones --- server/src/services/metadata.service.spec.ts | 23 ++++++++++++++++++++ server/src/services/metadata.service.ts | 21 +++++++----------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index 761521346aba8..cd7f68ab1dd8c 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -61,6 +61,8 @@ describe(MetadataService.name, () => { tagMock, userMock, } = newTestService(MetadataService)); + + delete process.env.TZ; }); afterEach(async () => { @@ -275,6 +277,27 @@ describe(MetadataService.name, () => { }); }); + it('should account for the server being in a non-UTC timezone', async () => { + process.env.TZ = 'America/Los_Angeles'; + assetMock.getByIds.mockResolvedValue([assetStub.sidecar]); + metadataMock.readTags.mockResolvedValueOnce({ + DateTimeOriginal: '2022:01:01 00:00:00', + }); + + await sut.handleMetadataExtraction({ id: assetStub.image.id }); + expect(assetMock.upsertExif).toHaveBeenCalledWith( + expect.objectContaining({ + dateTimeOriginal: new Date('2022-01-01T08:00:00.000Z'), + }), + ); + + expect(assetMock.update).toHaveBeenCalledWith( + expect.objectContaining({ + localDateTime: new Date('2022-01-01T00:00:00.000Z'), + }), + ); + }); + it('should handle lists of numbers', async () => { assetMock.getByIds.mockResolvedValue([assetStub.image]); metadataMock.readTags.mockResolvedValue({ ISO: [160] }); diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 57668355df636..a81d1b4904c27 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -577,13 +577,6 @@ export class MetadataService extends BaseService { const dateTime = firstDateTime(exifTags as Maybe, EXIF_DATE_TAGS); this.logger.debug(`Asset ${asset.id} date time is ${dateTime}`); - // created - let dateTimeOriginal = dateTime?.toDate(); - if (!dateTimeOriginal) { - this.logger.warn(`Asset ${asset.id} has no valid date (${dateTime}), falling back to asset.fileCreatedAt`); - dateTimeOriginal = asset.fileCreatedAt; - } - // timezone let timeZone = exifTags.tz ?? null; if (timeZone == null && dateTime?.rawValue?.endsWith('+00:00')) { @@ -598,14 +591,16 @@ export class MetadataService extends BaseService { this.logger.warn(`Asset ${asset.id} has no time zone information`); } - // offset minutes - const offsetMinutes = dateTime?.tzoffsetMinutes || 0; - let localDateTime = dateTimeOriginal; - if (offsetMinutes) { - localDateTime = new Date(dateTimeOriginal.getTime() + offsetMinutes * 60_000); - this.logger.debug(`Asset ${asset.id} local time is offset by ${offsetMinutes} minutes`); + let dateTimeOriginal = dateTime?.toDate(); + let localDateTime = dateTime?.toDateTime().setZone('UTC', { keepLocalTime: true }).toJSDate(); + if (!localDateTime || !dateTimeOriginal) { + this.logger.warn(`Asset ${asset.id} has no valid date, falling back to asset.fileCreatedAt`); + dateTimeOriginal = asset.fileCreatedAt; + localDateTime = asset.fileCreatedAt; } + this.logger.debug(`Asset ${asset.id} has a local time of ${localDateTime.toISOString()}`); + let modifyDate = asset.fileModifiedAt; try { modifyDate = (exifTags.ModifyDate as ExifDateTime)?.toDate() ?? modifyDate;