Skip to content

Commit 6f916c9

Browse files
support dates prior 1970-01-01 on non-linux #1045 (#1099)
1 parent 3c1e59f commit 6f916c9

File tree

4 files changed

+115
-9
lines changed

4 files changed

+115
-9
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- fix: dates prior 1970 do not work on non linux [#1045](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/1045)
6+
57
## 1.27.1 (2025-03-16)
68

79
- fix: disambiguate whole photo collection from the album with `All Photos` name [#1077](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/1077)

src/icloudpd/download.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ def update_mtime(created: datetime.datetime, download_path: str) -> None:
3535

3636
def set_utime(download_path: str, created_date: datetime.datetime) -> None:
3737
"""Set date & time of the file"""
38-
ctime = time.mktime(created_date.timetuple())
38+
try:
39+
ctime = time.mktime(created_date.timetuple())
40+
except OverflowError:
41+
ctime = time.mktime(datetime.datetime(1970, 1, 1, 0, 0, 0).timetuple())
3942
os.utime(download_path, (ctime, ctime))
4043

4144

tests/test_download_photos.py

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import piexif
1313
import pytest
14+
import pytz
1415
from click.testing import CliRunner
1516
from piexif._exceptions import InvalidImageDataError
1617
from requests import Response
@@ -942,19 +943,21 @@ def astimezone(self, _tz: (Optional[Any]) = None) -> NoReturn:
942943

943944
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
944945
@pytest.mark.skipif(sys.platform == "darwin", reason="does not run on mac")
945-
def test_invalid_creation_year(self) -> None:
946+
def test_creation_date_without_century(self) -> None:
946947
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])
947948

948949
files_to_download = [("5/01/01", "IMG_7409.JPG")]
949950

950951
with mock.patch.object(PhotoAsset, "created", new_callable=mock.PropertyMock) as dt_mock:
951952
# Can't mock `astimezone` because it's a readonly property, so have to
952953
# create a new class that inherits from datetime.datetime
953-
class NewDateTime(datetime.datetime):
954-
def astimezone(self, _tz: (Optional[Any]) = None) -> NoReturn:
955-
raise ValueError("Invalid date")
954+
# class NewDateTime(datetime.datetime):
955+
# def astimezone(self, _tz: (Optional[Any]) = None) -> NoReturn:
956+
# raise ValueError("Invalid date")
956957

957-
dt_mock.return_value = NewDateTime(5, 1, 1, 0, 0, 0)
958+
dt_mock.return_value = datetime.datetime(
959+
5, 1, 1, 0, 0, 0, tzinfo=pytz.timezone("America/Los_Angeles")
960+
)
958961

959962
data_dir, result = run_icloudpd_test(
960963
self.assertEqual,
@@ -985,12 +988,60 @@ def astimezone(self, _tz: (Optional[Any]) = None) -> NoReturn:
985988
f"INFO Downloading the first original photo or video to {data_dir} ...",
986989
self._caplog.text,
987990
)
991+
# self.assertIn(
992+
# "ERROR Could not convert photo created date to local timezone (0005-01-01 00:00:00)",
993+
# self._caplog.text,
994+
# )
995+
self.assertIn(
996+
f"DEBUG Downloading {os.path.join(data_dir, os.path.normpath('5/01/01/IMG_7409.JPG'))}",
997+
self._caplog.text,
998+
)
999+
self.assertIn("INFO All photos have been downloaded", self._caplog.text)
1000+
assert result.exit_code == 0
1001+
1002+
def test_creation_date_prior_1970(self) -> None:
1003+
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])
1004+
1005+
files_to_download = [("1965/01/01", "IMG_7409.JPG")]
1006+
1007+
with mock.patch.object(PhotoAsset, "created", new_callable=mock.PropertyMock) as dt_mock:
1008+
# Can't mock `astimezone` because it's a readonly property, so have to
1009+
# create a new class that inherits from datetime.datetime
1010+
# class NewDateTime(datetime.datetime):
1011+
# def astimezone(self, _tz: (Optional[Any]) = None) -> NoReturn:
1012+
# raise ValueError("Invalid date")
1013+
1014+
dt_mock.return_value = datetime.datetime(1965, 1, 1, 0, 0, 0)
1015+
1016+
data_dir, result = run_icloudpd_test(
1017+
self.assertEqual,
1018+
self.root_path,
1019+
base_dir,
1020+
"listing_photos.yml",
1021+
[],
1022+
files_to_download,
1023+
[
1024+
"--username",
1025+
"jdoe@gmail.com",
1026+
"--password",
1027+
"password1",
1028+
"--recent",
1029+
"1",
1030+
"--skip-live-photos",
1031+
"--no-progress-bar",
1032+
],
1033+
)
1034+
1035+
self.assertIn(
1036+
"DEBUG Looking up all photos and videos...",
1037+
self._caplog.text,
1038+
)
9881039
self.assertIn(
989-
"ERROR Could not convert photo created date to local timezone (0005-01-01 00:00:00)",
1040+
f"INFO Downloading the first original photo or video to {data_dir} ...",
9901041
self._caplog.text,
9911042
)
9921043
self.assertIn(
993-
f"DEBUG Downloading {os.path.join(data_dir, os.path.normpath('5/01/01/IMG_7409.JPG'))}",
1044+
f"DEBUG Downloading {os.path.join(data_dir, os.path.normpath('1965/01/01/IMG_7409.JPG'))}",
9941045
self._caplog.text,
9951046
)
9961047
self.assertIn("INFO All photos have been downloaded", self._caplog.text)

tests/test_download_photos_id.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,7 @@ def astimezone(self, _tz: (Optional[Any]) = None) -> NoReturn:
891891

892892
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
893893
@pytest.mark.skipif(sys.platform == "darwin", reason="does not run on mac")
894-
def test_invalid_creation_year_name_id7(self) -> None:
894+
def test_creation_date_without_century_name_id7(self) -> None:
895895
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])
896896

897897
files_to_download = [("5/01/01", "IMG_7409_QVk2Yyt.JPG")]
@@ -945,6 +945,56 @@ def astimezone(self, _tz: (Optional[Any]) = None) -> NoReturn:
945945
self.assertIn("INFO All photos have been downloaded", self._caplog.text)
946946
assert result.exit_code == 0
947947

948+
def test_creation_date_prior_1970_name_id7(self) -> None:
949+
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])
950+
951+
files_to_download = [("1965/01/01", "IMG_7409_QVk2Yyt.JPG")]
952+
953+
with mock.patch.object(PhotoAsset, "created", new_callable=mock.PropertyMock) as dt_mock:
954+
# Can't mock `astimezone` because it's a readonly property, so have to
955+
# create a new class that inherits from datetime.datetime
956+
# class NewDateTime(datetime.datetime):
957+
# def astimezone(self, _tz: (Optional[Any]) = None) -> NoReturn:
958+
# raise ValueError("Invalid date")
959+
960+
dt_mock.return_value = datetime.datetime(1965, 1, 1, 0, 0, 0)
961+
962+
data_dir, result = run_icloudpd_test(
963+
self.assertEqual,
964+
self.root_path,
965+
base_dir,
966+
"listing_photos.yml",
967+
[],
968+
files_to_download,
969+
[
970+
"--username",
971+
"jdoe@gmail.com",
972+
"--password",
973+
"password1",
974+
"--recent",
975+
"1",
976+
"--skip-live-photos",
977+
"--no-progress-bar",
978+
"--file-match-policy",
979+
"name-id7",
980+
],
981+
)
982+
983+
self.assertIn(
984+
"DEBUG Looking up all photos and videos...",
985+
self._caplog.text,
986+
)
987+
self.assertIn(
988+
f"INFO Downloading the first original photo or video to {data_dir} ...",
989+
self._caplog.text,
990+
)
991+
self.assertIn(
992+
f"DEBUG Downloading {os.path.join(data_dir, os.path.normpath('1965/01/01/IMG_7409_QVk2Yyt.JPG'))}",
993+
self._caplog.text,
994+
)
995+
self.assertIn("INFO All photos have been downloaded", self._caplog.text)
996+
assert result.exit_code == 0
997+
948998
def test_missing_item_type_name_id7(self) -> None:
949999
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])
9501000

0 commit comments

Comments
 (0)