Skip to content

Conversation

StarGest
Copy link
Contributor

@StarGest StarGest commented Sep 2, 2025

Description

When uploading an image via "share to" or "backup" without EXIF data indicating the date taken or created, the image is uploaded with an incorrect date. This occurs because fileCreatedAt is set using the file attribute change time (ctime) instead of the last modification time of the file's content (mtime).

  • ctime (changed): the time when the file's attributes (inode) were changed, such as permission or ownership changes.

  • mtime (modified): the time of the last modification of the file's content, which more accurately reflects the time when the data within the file was first created.

It is expected that fileCreatedAt should indicate the time when the data within the file was first recorded. Using mtime for this purpose aligns better with these expectations than using ctime.

Fixes #15855

How Has This Been Tested?

The changes have been tested on Android: a file without EXIF data was successfully uploaded with the correct creation date through "share to."

Checklist:

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation if applicable
  • I have no unrelated changes in the PR.
  • I have confirmed that any new dependencies are strictly necessary.
  • I have written tests for new code (if applicable)
  • I have followed naming conventions/patterns in the surrounding code
  • All code in src/services/ uses repositories implementations for database calls, filesystem operations, etc.
  • All code in src/repositories/ is pretty basic/simple and does not have any immich specific logic (that belongs in src/services/)

@alextran1502
Copy link
Member

Do you know if this handles the issue of photos, such as screenshots, to put them into the correct date?

@StarGest
Copy link
Contributor Author

StarGest commented Sep 2, 2025

Do you know if this handles the issue of photos, such as screenshots, to put them into the correct date?

I tested this again and realized that it doesn't resolve the issue with photos, such as screenshots, or images received through WhatsApp and Telegram. I will investigate further to identify the underlying problem. In any case, I believe that this change is still beneficial, as using ctime is a more accurate approach in this context.

@shenlong-tanwen
Copy link
Member

In any case, I believe that this change is still beneficial, as using ctime is a more accurate approach in this context.

Why not use the createdAt field from the asset itself. This is from PhotoKit and MediaStore on iOS and Android respectively. The modified time on the other hand is not reliable on iOS, but we could use the modifiedAt from the asset on Android

@StarGest
Copy link
Contributor Author

StarGest commented Sep 2, 2025

Why not use the createdAt field from the asset itself. This is from PhotoKit and MediaStore on iOS and Android respectively. The modified time on the other hand is not reliable on iOS, but we could use the modifiedAt from the asset on Android

The use of PhotoKit and MediaStore does not promote cross-platform compatibility. In the absence of EXIF data, these APIs likely use mtime to determine the creation date, as it is the standard method for identifying the modification time of a file’s content. Your suggestion makes sense in certain contexts; however, using mtime provides a more consistent and reliable approach across all platforms.

@shenlong-tanwen
Copy link
Member

The use of PhotoKit and MediaStore does not promote cross-platform compatibility.

The local sync that stores the asset data on the DB already uses PhotoKit and MediaStore on the respective platforms to fetch the asset metadata. In-fact, the createdAt time from the above APIs are used to display the asset in it's correct time group in the main timeline. There are cases where the createdAt time could be wrong, like those from social media apps, but for most cases, the ones provided by the APIs is expected to be correct.

Our old backup was using the ones from the platform and we never had issues except for the ones from social media, which is an entirely different issue in itself.

We build the upload task in two places, one is from the share handler and the other is from the regular backup flow. Can you please update the code to use the createdAt from the asset in the regular flow, we can use the mtime in the share handler flow. If this still had issues, we can always create another PR to fix it

@shenlong-tanwen
Copy link
Member

@StarGest Do you mind me taking over the PR and updating the regular flow to use the createdAt time from the asset?

@StarGest
Copy link
Contributor Author

StarGest commented Sep 3, 2025

@StarGest Do you mind me taking over the PR and updating the regular flow to use the createdAt time from the asset?

I have no objections to you taking over the PR and updating it. I think that retrieving information directly from native APIs would be more reliable.

I rarely work on mobile development and did not realize that when handling an incoming Shared Intent, we do not get direct access to the original files. Instead, we deal with a stream containing the file's data, MIME type, and name. This means there is no way to extract attributes from the original file in the filesystem, such as the creation date, since we are interacting with a data stream rather than an actual file.

During the process, a call to file.stat() occurs in ShareIntentUploadStateNotifier, which retrieves information from the filesystem about a newly created file copy generated from the stream in the app's cache. Since this new copy is created from the stream, it does not contain the original metadata, such as the creation date of the original file.

In this case, it may also be beneficial to utilize native APIs. This approach requires further investigation.

@shenlong-tanwen
Copy link
Member

I rarely work on mobile development and did not realize that when handling an incoming Shared Intent, we do not get direct access to the original files. Instead, we deal with a stream containing the file's data, MIME type, and name. This means there is no way to extract attributes from the original file in the filesystem, such as the creation date, since we are interacting with a data stream rather than an actual file.

During the process, a call to file.stat() occurs in ShareIntentUploadStateNotifier, which retrieves information from the filesystem about a newly created file copy generated from the stream in the app's cache. Since this new copy is created from the stream, it does not contain the original metadata, such as the creation date of the original file.

In this case, it may also be beneficial to utilize native APIs. This approach requires further investigation.

You understanding is correct. That is the case on Android. iOS on the other hand, never gives us access to the underlying file. You can either copy it into your app's sandbox or use the PhotoKit APIs to fetch the required data.

I've now updated the change to use the ctime still for the assets that are coming in through the share intent. For the regular backup flow, we now use the createdAt and updatedAt values from the asset. We could update the share intent handler to search if we have the details of the asset synced and use the details in a later PR

@riccardoruspoli
Copy link
Contributor

Hello, I opened #21577 earlier, which is now closed and redirected here. The mobile logic is clear to me; it's the same thing as I did, except applied globally and not only on iOS. But without the server-side change reported in #21577, the server keeps applying the stats date over the asset date, thus showing an incorrect date in the timeline for the involved assets.

@shenlong-tanwen
Copy link
Member

shenlong-tanwen commented Sep 4, 2025

But without the server-side change reported in #21577, the server keeps applying the stats date over the asset date, thus showing an incorrect date in the timeline for the involved assets.

The stats mtime actually come from the DTO sent from the mobile app since we utime it on upload. However, it is true that the fileCreatedAt value is lost between all these process.

Can you open a separate server only PR to handle this? You can also send a message in the Contributors channel over at discord to discuss the change before opening a PR. This is a delicate area where a change made could affect all uploads to the server and so it is best to discuss it over. Thanks!

@alextran1502 alextran1502 enabled auto-merge (squash) September 4, 2025 18:19
@alextran1502 alextran1502 merged commit 51aec1e into immich-app:main Sep 4, 2025
44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[mobile] Image date not respected when uploading
4 participants