diff --git a/web/packages/new/photos/services/exif-update.ts b/web/packages/new/photos/services/exif-update.ts index 77f1b3c212..208c3a661f 100644 --- a/web/packages/new/photos/services/exif-update.ts +++ b/web/packages/new/photos/services/exif-update.ts @@ -52,7 +52,7 @@ export const updateExifIfNeededAndPossible = async ( } catch (e) { log.error(`Failed to modify Exif date for ${fileName}`, e); // We used the file's extension to determine if this was a JPEG, but - // this is not a guarantee. Misnamed files, while rare, do exist. So in + // this is not a guarantee. Misnamed files, while rare, do exist. So if // that is the error thrown by the underlying library, fallback to the // original instead of causing the entire download or export to fail. if ( @@ -125,39 +125,53 @@ const dataURLToBlob = (dataURI: string) => * * [Note: Exif dates] * + * Summary: + * + * DateTimeOriginal is in local time, not UTC. Which local time? The + * OffsetTimeOriginal specifies the time zone. But support for it is limited. + * + * Details: + * * Common Exif date time tags, an in particular "DateTimeOriginal", are * specified in the form: * * yyyy:MM:DD HH:mm:ss * - * These values thus do not have an associated UTC offset or TZ. The common - * convention (based on my current understanding) is that these times are - * interpreted to be the local time where the photo was taken. + * These values thus do not have an associated UTC offset or TZ. * - * Recently, there seems to be increasing support for the (newly standardized) - * "OffsetTimeOriginal" and related fields, which specifies time zone for + * The common convention is that these times are interpreted to be the local + * time where the photo was taken. + * + * > The reason (I assume) for this omission is because (prior to smartphones), + * > cameras did not have a good way of being aware of their time zone. + * + * Recently, there seems to be increasing support for the newly standardized + * "OffsetTimeOriginal" and siblings, which specify time zone for * "DateTimeOriginal" (and related fields). * - * However, when the offset time tag is not present (a frequent occurrence, not - * just for older photos but also for screenshots generated by OSes as of 2024), - * we don't really know, and stick with the common convention: + * However, support is still not universal, and there will anyways be older + * photos. So when the offset is missing, we stick with the common convention: * * - When reading, assume that the Exif date is in the local TZ when deriving * a UTC timestamp from it. * * - When writing, convert the UTC timestamp to local time. + * + * These assumptions are fallible, because the local time where we're uploading + * or downloading the photo is not necessarily the same as the local time where + * the photo was taken. */ const convertToExifDateFormat = (date: Date) => { // TODO: Exif - Handle offsettime if present const yyyy = date.getFullYear(); - const MM = zeroPad2(date.getMonth() + 1); - const dd = zeroPad2(date.getDate()); - const HH = zeroPad2(date.getHours()); - const mm = zeroPad2(date.getMinutes()); - const ss = zeroPad2(date.getSeconds()); + const MM = zeroPad(date.getMonth() + 1, 2); + const dd = zeroPad(date.getDate(), 2); + const HH = zeroPad(date.getHours(), 2); + const mm = zeroPad(date.getMinutes(), 2); + const ss = zeroPad(date.getSeconds(), 2); return `${yyyy}:${MM}:${dd} ${HH}:${mm}:${ss}`; }; -/** Zero pad the given number to 2 digits. */ -const zeroPad2 = (n: number) => (n < 10 ? `0${n}` : `${n}`); +/** Zero pad the given number to {@link d} digits. */ +const zeroPad = (n: number, d: number) => n.toString().padStart(d, "0");