diff --git a/demo/images/4MinsAroundTheWorld/20240413-203800-Unknown.jpg b/demo/images/4MinsAroundTheWorld/20240413-203800-Unknown.jpg new file mode 100644 index 00000000..4035cdb9 Binary files /dev/null and b/demo/images/4MinsAroundTheWorld/20240413-203800-Unknown.jpg differ diff --git a/demo/images/4MinsAroundTheWorld/UTC-20240413-203430-Auckland.jpg b/demo/images/4MinsAroundTheWorld/UTC-20240413-203430-Auckland.jpg new file mode 100644 index 00000000..e2245adb Binary files /dev/null and b/demo/images/4MinsAroundTheWorld/UTC-20240413-203430-Auckland.jpg differ diff --git a/demo/images/4MinsAroundTheWorld/UTC-20240413-203530-Montevideo.jpg b/demo/images/4MinsAroundTheWorld/UTC-20240413-203530-Montevideo.jpg new file mode 100644 index 00000000..3102d805 Binary files /dev/null and b/demo/images/4MinsAroundTheWorld/UTC-20240413-203530-Montevideo.jpg differ diff --git a/demo/images/4MinsAroundTheWorld/UTC-20240413-203531-Kathmandu.jpg b/demo/images/4MinsAroundTheWorld/UTC-20240413-203531-Kathmandu.jpg new file mode 100644 index 00000000..bf3cbe96 Binary files /dev/null and b/demo/images/4MinsAroundTheWorld/UTC-20240413-203531-Kathmandu.jpg differ diff --git a/demo/images/4MinsAroundTheWorld/UTC-20240413-203630-London.jpg b/demo/images/4MinsAroundTheWorld/UTC-20240413-203630-London.jpg new file mode 100644 index 00000000..b59dc854 Binary files /dev/null and b/demo/images/4MinsAroundTheWorld/UTC-20240413-203630-London.jpg differ diff --git a/demo/images/4MinsAroundTheWorld/UTC-20240413-203720-Tokyo.jpg b/demo/images/4MinsAroundTheWorld/UTC-20240413-203720-Tokyo.jpg new file mode 100644 index 00000000..9efd4ded Binary files /dev/null and b/demo/images/4MinsAroundTheWorld/UTC-20240413-203720-Tokyo.jpg differ diff --git a/demo/images/4MinsAroundTheWorld/UTC-20240413-203730-Reykjavik.jpg b/demo/images/4MinsAroundTheWorld/UTC-20240413-203730-Reykjavik.jpg new file mode 100644 index 00000000..852bfc50 Binary files /dev/null and b/demo/images/4MinsAroundTheWorld/UTC-20240413-203730-Reykjavik.jpg differ diff --git a/demo/images/4MinsAroundTheWorld/UTC-20240413-203830-NewYork.jpg b/demo/images/4MinsAroundTheWorld/UTC-20240413-203830-NewYork.jpg new file mode 100644 index 00000000..62cb4584 Binary files /dev/null and b/demo/images/4MinsAroundTheWorld/UTC-20240413-203830-NewYork.jpg differ diff --git a/src/backend/model/database/SearchManager.ts b/src/backend/model/database/SearchManager.ts index 2e3bb8c5..72163c3a 100644 --- a/src/backend/model/database/SearchManager.ts +++ b/src/backend/model/database/SearchManager.ts @@ -364,7 +364,11 @@ export class SearchManager { for (const sort of sortings) { switch (sort.method) { case SortByTypes.Date: - query.addOrderBy('media.metadata.creationDate', sort.ascending ? 'ASC' : 'DESC'); //TODO: Offset: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). If taken into account, it will alter the sort order. Probably should not be done. + if (Config.Gallery.ignoreTimestampOffset === true) { + query.addOrderBy('media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)', sort.ascending ? 'ASC' : 'DESC'); + } else { + query.addOrderBy('media.metadata.creationDate', sort.ascending ? 'ASC' : 'DESC'); + } break; case SortByTypes.Rating: query.addOrderBy('media.metadata.rating', sort.ascending ? 'ASC' : 'DESC'); @@ -562,15 +566,17 @@ export class SearchManager { const textParam: { [key: string]: unknown } = {}; textParam['from' + queryId] = (query as FromDateSearch).value; - q.where( - `media.metadata.creationDate ${relation} :from${queryId}`, //TODO: Offset: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). - //Example: -600 means in the database UTC-10:00. The time 20:00 in the evening in the UTC-10 timezone, is actually 06:00 the next morning - //in UTC+00:00. To make search take that into account, one can subtract the offset from the creationDate to "pretend" the photo is taken - //in UTC time. Subtracting -600 minutes (because it's the -10:00 timezone), corresponds to adding 10 hours to the photo's timestamp, thus - //bringing it into the next day as if it was taken at UTC+00:00. Similarly subtracting a positive timezone from a timestamp will "pretend" - //the photo is taken earlier in time (e.g. subtracting 300 from the UTC+05:00 timezone). - textParam - ); + if (Config.Gallery.ignoreTimestampOffset === true) { + q.where( + `(media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)) ${relation} :from${queryId}`, + textParam + ); + } else { + q.where( + `media.metadata.creationDate ${relation} :from${queryId}`, + textParam + ); + } return q; }); @@ -589,10 +595,18 @@ export class SearchManager { const textParam: { [key: string]: unknown } = {}; textParam['to' + queryId] = (query as ToDateSearch).value; - q.where( - `media.metadata.creationDate ${relation} :to${queryId}`, //TODO: Offset: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. - textParam - ); + if (Config.Gallery.ignoreTimestampOffset === true) { + q.where( + `(media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)) ${relation} :to${queryId}`, + textParam + ); + } else { + q.where( + `media.metadata.creationDate ${relation} :to${queryId}`, + textParam + ); + + } return q; }); @@ -793,18 +807,25 @@ export class SearchManager { textParam['to' + queryId] = to.getTime(); textParam['from' + queryId] = from.getTime(); if (tq.negate) { + if (Config.Gallery.ignoreTimestampOffset === true) { + q.where( + `(media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)) >= :to${queryId}`, + textParam + ).orWhere(`media.metadata.creationDate < :from${queryId}`, + textParam); + } else { - q.where( - `media.metadata.creationDate >= :to${queryId}`, //TODO: Offset: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. - textParam - ).orWhere(`media.metadata.creationDate < :from${queryId}`, //TODO: Offset: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. - textParam); + } } else { - q.where( - `media.metadata.creationDate < :to${queryId}`, //TODO: Offset: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. - textParam - ).andWhere(`media.metadata.creationDate >= :from${queryId}`, //TODO: Offset: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. - textParam); + if (Config.Gallery.ignoreTimestampOffset === true) { + q.where( + `(media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)) < :to${queryId}`, + textParam + ).andWhere(`media.metadata.creationDate >= :from${queryId}`, + textParam); + } else { + + } } } else { @@ -824,30 +845,54 @@ export class SearchManager { if (Config.Database.type === DatabaseType.sqlite) { + //(media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)) + if (tq.daysLength == 0) { - q.where( - //TODO: Offset: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. - `CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationEql} CAST(strftime('${duration}','now') AS INTEGER)` - ); + if (Config.Gallery.ignoreTimestampOffset === true) { + q.where( + `CAST(strftime('${duration}',(media.metadataCreationDate + (media.metadataCreationDateOffset * 60000))/1000, 'unixepoch') AS INTEGER) ${relationEql} CAST(strftime('${duration}','now') AS INTEGER)` + ); + } else { + q.where( + `CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationEql} CAST(strftime('${duration}','now') AS INTEGER)` + ); + } } else { - q.where( - //TODO: Offset: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. - `CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('${duration}','now') AS INTEGER)` - )[whereFN](`CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('${duration}','now','-:diff${queryId} day') AS INTEGER)`, - textParam); + if (Config.Gallery.ignoreTimestampOffset === true) { + q.where( + `CAST(strftime('${duration}',(media.metadataCreationDate + (media.metadataCreationDateOffset * 60000))/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('${duration}','now') AS INTEGER)` + )[whereFN](`CAST(strftime('${duration}',(media.metadataCreationDate + (media.metadataCreationDateOffset * 60000))/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('${duration}','now','-:diff${queryId} day') AS INTEGER)`, + textParam); + } else { + q.where( + `CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('${duration}','now') AS INTEGER)` + )[whereFN](`CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('${duration}','now','-:diff${queryId} day') AS INTEGER)`, + textParam); + } } } else { if (tq.daysLength == 0) { - q.where( - //TODO: Offset: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. - `CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationEql} CAST(DATE_FORMAT(CURDATE(),'${duration}') AS SIGNED)` - ); + if (Config.Gallery.ignoreTimestampOffset === true) { + q.where( + `CAST(FROM_UNIXTIME((media.metadataCreationDate + (media.metadataCreationDateOffset * 60000))/1000, '${duration}') AS SIGNED) ${relationEql} CAST(DATE_FORMAT(CURDATE(),'${duration}') AS SIGNED)` + ); + } else { + q.where( + `CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationEql} CAST(DATE_FORMAT(CURDATE(),'${duration}') AS SIGNED)` + ); + } } else { - q.where( - //TODO: Offset: If media.metadata.creationDateOffset is defined, it is an offset of minutes (+/-). See explanation above. - `CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationTop} CAST(DATE_FORMAT(CURDATE(),'${duration}') AS SIGNED)` - )[whereFN](`CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationBottom} CAST(DATE_FORMAT((DATE_ADD(curdate(), INTERVAL -:diff${queryId} DAY)),'${duration}') AS SIGNED)`, - textParam); + if (Config.Gallery.ignoreTimestampOffset === true) { + q.where( + `CAST(FROM_UNIXTIME((media.metadataCreationDate + (media.metadataCreationDateOffset * 60000))/1000, '${duration}') AS SIGNED) ${relationTop} CAST(DATE_FORMAT(CURDATE(),'${duration}') AS SIGNED)` + )[whereFN](`CAST(FROM_UNIXTIME((media.metadataCreationDate + (media.metadataCreationDateOffset * 60000))/1000, '${duration}') AS SIGNED) ${relationBottom} CAST(DATE_FORMAT((DATE_ADD(curdate(), INTERVAL -:diff${queryId} DAY)),'${duration}') AS SIGNED)`, + textParam); + } else { + q.where( + `CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationTop} CAST(DATE_FORMAT(CURDATE(),'${duration}') AS SIGNED)` + )[whereFN](`CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationBottom} CAST(DATE_FORMAT((DATE_ADD(curdate(), INTERVAL -:diff${queryId} DAY)),'${duration}') AS SIGNED)`, + textParam); + } } } }; diff --git a/src/backend/model/fileaccess/DiskManager.ts b/src/backend/model/fileaccess/DiskManager.ts index 2c769b67..b8f38fbe 100644 --- a/src/backend/model/fileaccess/DiskManager.ts +++ b/src/backend/model/fileaccess/DiskManager.ts @@ -277,8 +277,8 @@ export class DiskManager { directory.oldestMedia = Number.MIN_SAFE_INTEGER; directory.media.forEach((m) => { - directory.youngestMedia = Math.min(m.metadata.creationDate, directory.youngestMedia); //TODO: Offset: - directory.oldestMedia = Math.max(m.metadata.creationDate, directory.oldestMedia); + directory.youngestMedia = Math.min(Utils.getTimeMS(m.metadata.creationDate, m.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset), directory.youngestMedia); + directory.oldestMedia = Math.max(Utils.getTimeMS(m.metadata.creationDate, m.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset), directory.oldestMedia); } ); diff --git a/src/backend/model/messenger/EmailMessenger.ts b/src/backend/model/messenger/EmailMessenger.ts index 6e2ed138..ed96f1d9 100644 --- a/src/backend/model/messenger/EmailMessenger.ts +++ b/src/backend/model/messenger/EmailMessenger.ts @@ -70,7 +70,7 @@ export class EmailMessenger extends Messenger<{ (media[i].metadata as PhotoMetadata).positionData?.country : ((media[i].metadata as PhotoMetadata).positionData?.city ? (media[i].metadata as PhotoMetadata).positionData?.city : ''); - const caption = Utils.getFullYear(media[i].metadata.creationDate, media[i].metadata.creationDateOffset) + (location ? ', ' + location : ''); //TODO: Offset: + const caption = Utils.getFullYear(Utils.getTimeMS(media[i].metadata.creationDate, media[i].metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset), undefined) + (location ? ', ' + location : ''); attachments.push({ filename: media[i].name, path: media[i].thumbnailPath, diff --git a/src/backend/model/messenger/Messenger.ts b/src/backend/model/messenger/Messenger.ts index d48f6506..2b867f74 100644 --- a/src/backend/model/messenger/Messenger.ts +++ b/src/backend/model/messenger/Messenger.ts @@ -33,7 +33,7 @@ export abstract class Messenger = Record 0 && (input as MediaDTO[])[0]?.name && (input as MediaDTO[])[0]?.directory - && (input as MediaDTO[])[0]?.metadata?.creationDate) { //TODO: Offset: + && (input as MediaDTO[])[0]?.metadata?.creationDate) { const media = input as MediaDTOWithThPath[]; for (let i = 0; i < media.length; ++i) { media[i].thumbnailPath = await this.getThumbnail(media[i]); diff --git a/src/common/Utils.ts b/src/common/Utils.ts index 776bff2b..543247d1 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -227,11 +227,20 @@ export class Utils { } //Get the MS of the creationDate, adjusted for the offset. Effectively getting the MS value as if the photo did not contain an offset. - //One can consider this "Local" time of the photo. + //One can consider this "Local" time of the photo. Starting point is UTC, as MetadataLoader loads timestamps with unknown timestamps as UTC. static getLocalTimeMS(creationDate: number, creationDateOffset: string) { const offsetMinutes = Utils.getOffsetMinutes(creationDateOffset); return creationDate + (offsetMinutes ? (offsetMinutes * 60000) : 0); } + + //Like getLocalTimeMS, but only if localTime is true, otherwise just returns creationDate (global time) + static getTimeMS(creationDate: number, creationDateOffset: string, localTime: boolean) { + if (localTime) { + return Utils.getLocalTimeMS(creationDate, creationDateOffset); + } else { + return creationDate; + } + } static isLeapYear(year: number) { return (0 == year % 4) && (0 != year % 100) || (0 == year % 400) diff --git a/src/common/config/public/ClientConfig.ts b/src/common/config/public/ClientConfig.ts index 5db92a75..c6adfe09 100644 --- a/src/common/config/public/ClientConfig.ts +++ b/src/common/config/public/ClientConfig.ts @@ -1089,7 +1089,10 @@ export class ClientGalleryConfig { }, description: $localize`If enabled, timestamp offsets are ignored, meaning that the local times of pictures are used for searching, sorting and grouping. If disabled, global time is used and pictures with no timestamp are assumed to be in UTC (offset +00:00).` }) - ignoreTimestampOffset: boolean = true; + //DEVELOPER NOTE: The Database model stores the timestamp (creationDate) as milliseconds since 1970-01-01 UTC (global time). And stores and offset (creationDateOffset) as minutes. + //Ignoring timestamp for the user is the opposite for the database. If the user wants to ignore the offset, we have to add the offset to the creationDate to give the user the right experience. + ignoreTimestampOffset: boolean = true; + @ConfigProperty({ tags: { diff --git a/src/frontend/app/ui/duplicates/duplicates.component.html b/src/frontend/app/ui/duplicates/duplicates.component.html index 5c2d1678..f1168a73 100644 --- a/src/frontend/app/ui/duplicates/duplicates.component.html +++ b/src/frontend/app/ui/duplicates/duplicates.component.html @@ -23,7 +23,7 @@
{{media.metadata.fileSize | fileSize}}
-
+
{{ media.metadata.creationDate | date : 'longDate' : (media.metadata.creationDateOffset ? media.metadata.creationDateOffset : 'UTC') }}, {{ media.metadata.creationDate | date : (media.metadata.creationDateOffset ? 'HH:mm:ss ZZZZZ' : 'HH:mm:ss') : (media.metadata.creationDateOffset ? media.metadata.creationDateOffset : 'UTC') }}
diff --git a/src/frontend/app/ui/gallery/blog/blog.service.ts b/src/frontend/app/ui/gallery/blog/blog.service.ts index 73a8580c..34cb0a25 100644 --- a/src/frontend/app/ui/gallery/blog/blog.service.ts +++ b/src/frontend/app/ui/gallery/blog/blog.service.ts @@ -6,6 +6,7 @@ import {ContentService} from '../content.service'; import {mergeMap, Observable, shareReplay} from 'rxjs'; import {MDFilesFilterPipe} from '../../../pipes/MDFilesFilterPipe'; import {MDFileDTO} from '../../../../../common/entities/MDFileDTO'; +import {Config} from '../../../../../common/config/public/Config'; @Injectable() export class BlogService { @@ -28,7 +29,7 @@ export class BlogService { let firstMedia = Number.MAX_SAFE_INTEGER; if (content.mediaGroups.length > 0) { firstMedia = content.mediaGroups[0].media.reduce((p, m) => - Math.min(m.metadata.creationDate, p), Number.MAX_SAFE_INTEGER); //TODO: Offset: + Math.min(Utils.getTimeMS(m.metadata.creationDate, m.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset), p), Number.MAX_SAFE_INTEGER); } const files = this.mdFilesFilterPipe.transform(content.metaFile) diff --git a/src/frontend/app/ui/gallery/filter/filter.service.ts b/src/frontend/app/ui/gallery/filter/filter.service.ts index ca84ff24..9d954822 100644 --- a/src/frontend/app/ui/gallery/filter/filter.service.ts +++ b/src/frontend/app/ui/gallery/filter/filter.service.ts @@ -3,6 +3,8 @@ import {BehaviorSubject, Observable} from 'rxjs'; import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; import {DirectoryContent} from '../contentLoader.service'; import {map, switchMap} from 'rxjs/operators'; +import {Config} from '../../../../../common/config/public/Config'; +import {Utils} from '../../../../../common/Utils'; export enum FilterRenderType { enum = 1, @@ -159,11 +161,11 @@ export class FilterService { } const ret: { date: Date, endDate: Date, dateStr: string, count: number, max: number }[] = []; const minDate = prefiltered.media.reduce( - (p, curr) => Math.min(p, curr.metadata.creationDate), //TODO: Offset: + (p, curr) => Math.min(p, Utils.getTimeMS(curr.metadata.creationDate, curr.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset)), Number.MAX_VALUE - 1 ); const maxDate = prefiltered.media.reduce( - (p, curr) => Math.max(p, curr.metadata.creationDate), //TODO: Offset: + (p, curr) => Math.max(p, Utils.getTimeMS(curr.metadata.creationDate, curr.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset)), Number.MIN_VALUE + 1 ); const diff = (maxDate - minDate) / 1000; @@ -205,7 +207,7 @@ export class FilterService { const startMediaDate = new Date(floorDate(minDate)); prefiltered.media.forEach(m => { - const key = Math.floor((floorDate(m.metadata.creationDate) - startMediaDate.getTime()) / 1000 / usedDiv); //TODO: Offset: + const key = Math.floor((floorDate(Utils.getTimeMS(m.metadata.creationDate, m.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset)) - startMediaDate.getTime()) / 1000 / usedDiv); const getDate = (index: number) => { let d: Date; @@ -273,16 +275,16 @@ export class FilterService { if (c.media.length > 0) { // Update date filter range afilters.dateFilter.minDate = c.media.reduce( - (p, curr) => Math.min(p, curr.metadata.creationDate), //TODO: Offset: + (p, curr) => Math.min(p, Utils.getTimeMS(curr.metadata.creationDate, curr.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset)), Number.MAX_VALUE - 1 ); afilters.dateFilter.maxDate = c.media.reduce( - (p, curr) => Math.max(p, curr.metadata.creationDate), //TODO: Offset: + (p, curr) => Math.max(p, Utils.getTimeMS(curr.metadata.creationDate, curr.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset)), Number.MIN_VALUE + 1 ); // Add a few sec padding - afilters.dateFilter.minDate -= (afilters.dateFilter.minDate % 1000) + 1000; - afilters.dateFilter.maxDate += (afilters.dateFilter.maxDate % 1000) + 1000; + afilters.dateFilter.minDate -= ((afilters.dateFilter.minDate % 1000) + 1000); + afilters.dateFilter.maxDate += ((1000 - (afilters.dateFilter.maxDate % 1000)) + 1000); if (afilters.dateFilter.minFilter === Number.MIN_VALUE) { afilters.dateFilter.minFilter = afilters.dateFilter.minDate; @@ -294,8 +296,8 @@ export class FilterService { // Apply Date filter c.media = c.media.filter( (m) => - m.metadata.creationDate >= afilters.dateFilter.minFilter && //TODO: Offset: - m.metadata.creationDate <= afilters.dateFilter.maxFilter //TODO: Offset: + Utils.getTimeMS(m.metadata.creationDate, m.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset) >= afilters.dateFilter.minFilter && + Utils.getTimeMS(m.metadata.creationDate, m.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset) <= afilters.dateFilter.maxFilter ); } else { afilters.dateFilter.minDate = Number.MIN_VALUE; diff --git a/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts b/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts index bde3467e..6cbc770c 100644 --- a/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts +++ b/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts @@ -508,7 +508,7 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges { case LightBoxTitleTexts.persons: return m.metadata.faces?.map(f => f.name)?.join(', '); case LightBoxTitleTexts.date: - return this.datePipe.transform(m.metadata.creationDate, 'longDate', m.metadata.creationDateOffset); //TODO: Offset: + return this.datePipe.transform(Utils.getTimeMS(m.metadata.creationDate, m.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset) , 'longDate', 'UTC'); case LightBoxTitleTexts.location: if (!m.metadata.positionData) { return ''; diff --git a/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html b/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html index 4bc7e82a..341aa65a 100644 --- a/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html +++ b/src/frontend/app/ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component.html @@ -54,7 +54,7 @@
- {{ media.metadata.creationDate | date: (isThisYear() ? 'MMMM d' : 'longDate') : (media.metadata.creationDateOffset ? media.metadata.creationDateOffset : 'UTC') }} + {{ media.metadata.creationDate | date: (isThisYear() ? 'MMMM d' : 'longDate') : (media.metadata.creationDateOffset ? media.metadata.creationDateOffset : 'UTC') }}
{{ media.metadata.creationDate | date : (media.metadata.creationDateOffset ? 'EEEE, HH:mm:ss ZZZZZ' : 'EEEE, HH:mm:ss') : (media.metadata.creationDateOffset ? media.metadata.creationDateOffset : 'UTC') }}
diff --git a/src/frontend/app/ui/gallery/navigator/sorting.service.ts b/src/frontend/app/ui/gallery/navigator/sorting.service.ts index d5a81993..8433859f 100644 --- a/src/frontend/app/ui/gallery/navigator/sorting.service.ts +++ b/src/frontend/app/ui/gallery/navigator/sorting.service.ts @@ -150,11 +150,7 @@ export class GallerySortingService { break; case SortByTypes.Date: media.sort((a: PhotoDTO, b: PhotoDTO): number => { - if (Config.Gallery.ignoreTimestampOffset === true) { - return Utils.getLocalTimeMS(a.metadata.creationDate, a.metadata.creationDateOffset) - Utils.getLocalTimeMS(b.metadata.creationDate, b.metadata.creationDateOffset); - } else { - return a.metadata.creationDate - b.metadata.creationDate; - } + return Utils.getTimeMS(a.metadata.creationDate, a.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset) - Utils.getTimeMS(b.metadata.creationDate, b.metadata.creationDateOffset, Config.Gallery.ignoreTimestampOffset); }); break; case SortByTypes.Rating: @@ -204,7 +200,7 @@ export class GallerySortingService { //Datepipe used this way, converts creationDate to date in local time. return (m: MediaDTO) => this.datePipe.transform(m.metadata.creationDate, 'longDate', m.metadata.creationDateOffset ? m.metadata.creationDateOffset : 'UTC'); } else { - //Grouping with global time, requires a common timeframe. Here and now it is UTC. (Could be config option later) //TODO: Configurable Default Timezone + //Grouping with global time, requires a common timeframe. return (m: MediaDTO) => this.datePipe.transform(m.metadata.creationDate, 'longDate', 'UTC'); } diff --git a/test/backend/assets/4MinsAroundTheWorld/20240413-203800-Unknown.jpg b/test/backend/assets/4MinsAroundTheWorld/20240413-203800-Unknown.jpg new file mode 100644 index 00000000..4035cdb9 Binary files /dev/null and b/test/backend/assets/4MinsAroundTheWorld/20240413-203800-Unknown.jpg differ diff --git a/test/backend/assets/4MinsAroundTheWorld/20240413-203800-Unknown.json b/test/backend/assets/4MinsAroundTheWorld/20240413-203800-Unknown.json new file mode 100644 index 00000000..aa1267f5 --- /dev/null +++ b/test/backend/assets/4MinsAroundTheWorld/20240413-203800-Unknown.json @@ -0,0 +1,8 @@ +{ + "creationDate": 1713040680000, + "fileSize": 71525, + "size": { + "height": 1080, + "width": 1920 + } +} diff --git a/test/backend/integration/model/sql/typeorm.ts b/test/backend/integration/model/sql/typeorm.ts index 0bfc65a0..39c40005 100644 --- a/test/backend/integration/model/sql/typeorm.ts +++ b/test/backend/integration/model/sql/typeorm.ts @@ -173,7 +173,7 @@ describe('Typeorm integration', () => { const photos = await pr .createQueryBuilder('media') - .orderBy('media.metadata.creationDate', 'ASC') //TODO: Offset: + .orderBy('media.metadata.creationDate', 'ASC') //TODO: Offset: Create a test where it is ".orderBy('media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)', 'ASC')" instead .where('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + photo.metadata.positionData.city + '%'}) .innerJoinAndSelect('media.directory', 'directory') .limit(10) @@ -195,7 +195,7 @@ describe('Typeorm integration', () => { await pr.save(photo); const photos = await pr .createQueryBuilder('media') - .orderBy('media.metadata.creationDate', 'ASC') //TODO: Offset: + .orderBy('media.metadata.creationDate', 'ASC') //TODO: Offset: Create a test where it is ".orderBy('media.metadata.creationDate + (media.metadata.creationDateOffset * 60000)', 'ASC')" instead .where('media.metadata.positionData.city LIKE :text COLLATE utf8_general_ci', {text: '%' + city + '%'}) .innerJoinAndSelect('media.directory', 'directory') .limit(10)