diff --git a/scripts/msys2-install-deps b/scripts/msys2-install-deps index 0cbf864242..19d940412d 100644 --- a/scripts/msys2-install-deps +++ b/scripts/msys2-install-deps @@ -1,6 +1,6 @@ #!/bin/bash -pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,lz4,openssl,rclone,python-msgpack,python-platformdirs,python,cython,python-setuptools,python-wheel,python-build,python-pkgconfig,python-packaging,python-pip,python-paramiko,rust,python-maturin} +pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,lz4,openssl,rclone,python-msgpack,python-platformdirs,python,cython,python-setuptools,python-setuptools-scm,python-wheel,python-build,python-pkgconfig,python-packaging,python-pip,python-paramiko,rust,python-maturin} if [ "$1" = "development" ]; then pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-python-{pytest,pytest-benchmark,pytest-cov,pytest-xdist} diff --git a/src/borg/helpers/time.py b/src/borg/helpers/time.py index b0610213fb..ef4f23ad01 100644 --- a/src/borg/helpers/time.py +++ b/src/borg/helpers/time.py @@ -24,7 +24,11 @@ def parse_local_timestamp(timestamp, tzinfo=None): """ dt = datetime.fromisoformat(timestamp) if dt.tzinfo is None: - dt = dt.astimezone(tz=tzinfo) + try: + dt = dt.astimezone(tz=tzinfo) + except OSError as e: + # e.g. win32 local time conversion fails for timestamps close to (or before) the epoch. + raise ValueError(f"cannot convert {timestamp} to local time: {e}") from None return dt diff --git a/src/borg/testsuite/archiver/completion_cmd_test.py b/src/borg/testsuite/archiver/completion_cmd_test.py index 24a501ed94..6b582947e6 100644 --- a/src/borg/testsuite/archiver/completion_cmd_test.py +++ b/src/borg/testsuite/archiver/completion_cmd_test.py @@ -615,12 +615,14 @@ def test_completion_repo_is_a_directory(archivers, request, shell): def test_bash_repo_url_no_directories(archivers, request, tmp_path): archiver = request.getfixturevalue(archivers) (tmp_path / "somerepo").mkdir() - for url in REPO_URLS: - # a local directory looking like the start of the URL, so that plain directory completion finds it - (tmp_path / url.split("/")[0]).mkdir(exist_ok=True) + # local directories looking like the start of the URLs, so that plain directory completion finds them. + # created by bash: on win32, MSYS2/Cygwin map the colon to a valid file name character. + dirs = " ".join(sorted({url.split("/")[0] for url in REPO_URLS})) script = cmd(archiver, "completion", "bash") for url in REPO_URLS: - result = _run_bash_completion_fn(script, f'cd "{tmp_path}"\n_borg_complete_repo_dirs "{url}"\n') + result = _run_bash_completion_fn( + script, f'cd "{tmp_path}"\nmkdir -p {dirs}\n_borg_complete_repo_dirs "{url}"\n' + ) assert result.returncode == 0, f"stderr: {result.stderr}" assert result.stdout == "", f"directories offered for {url}: {result.stdout}" result = _run_bash_completion_fn(script, f'cd "{tmp_path}"\n_borg_complete_repo_dirs some\n') diff --git a/src/borg/testsuite/archiver/recreate_cmd_test.py b/src/borg/testsuite/archiver/recreate_cmd_test.py index af140f7691..a0a07fd58f 100644 --- a/src/borg/testsuite/archiver/recreate_cmd_test.py +++ b/src/borg/testsuite/archiver/recreate_cmd_test.py @@ -262,9 +262,10 @@ def test_recreate_with_given_timestamp(archivers, request): # this shall recreate the archive with a different nominal timestamp. # --timestamp is the only change requested, so this also checks that a timestamp # change alone is enough to make recreate do the work instead of skipping the archive. - cmd(archiver, "recreate", "-a", "test0", "--timestamp", "1970-01-02T00:00:00") + # not 1970-01-02: win32 cannot convert that to local time east of UTC. + cmd(archiver, "recreate", "-a", "test0", "--timestamp", "1970-01-03T00:00:00") info = cmd(archiver, "info", "-a", "test0").splitlines() - dtime = datetime(1970, 1, 2, 0, 0, 0).astimezone() # local time in local timezone + dtime = datetime(1970, 1, 3, 0, 0, 0).astimezone() # local time in local timezone s_time = dtime.strftime("%Y-%m-%d %H:%M:.. %z").replace("+", r"\+") assert any([re.search(r"Time \(nominal\).+ %s" % s_time, item) for item in info]) # start/end time are just from the recreate operation diff --git a/src/borg/testsuite/helpers/time_test.py b/src/borg/testsuite/helpers/time_test.py index 8b929213d2..44ba2da92e 100644 --- a/src/borg/testsuite/helpers/time_test.py +++ b/src/borg/testsuite/helpers/time_test.py @@ -3,7 +3,8 @@ from ...helpers.time import safe_ns, safe_s, safe_timestamp, SUPPORT_32BIT_PLATFORMS, calculate_relative_offset from ...helpers.time import format_time, format_time_ns, OutputTimestamp -from ...helpers.time import ns_to_pax_time, pax_time_to_ns +from ...helpers.time import ns_to_pax_time, pax_time_to_ns, parse_local_timestamp +from ...helpers import time as time_module def utcfromtimestamp(timestamp): @@ -107,3 +108,14 @@ def test_ns_to_pax_time(ns, expected): ) def test_pax_time_to_ns(value, expected): assert pax_time_to_ns(value) == expected + + +def test_parse_local_timestamp_oserror(monkeypatch): + # win32 fails to convert 1970-01-02T00:00:00 to local time east of UTC. + class FailingDatetime(datetime): + def astimezone(self, tz=None): + raise OSError(22, "Invalid argument") + + monkeypatch.setattr(time_module, "datetime", FailingDatetime) + with pytest.raises(ValueError, match="cannot convert 1970-01-02T00:00:00 to local time"): + parse_local_timestamp("1970-01-02T00:00:00")