From 04940e3d44480c91f440c8060e4db9ba09580897 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 2 Mar 2026 22:49:50 +0100 Subject: [PATCH 1/4] helpers/time: avoid floating point in timestamp handling Add utcfromtimestampns() helper that converts nanosecond timestamps to datetime objects using integer arithmetic (timedelta) instead of floating point division. This avoids precision loss and potential overflow on 32bit platforms with old glibc. Use it in safe_timestamp() and timestamp() instead of datetime.fromtimestamp(). Needed to tweak the timestamps in repo12.tar.gz/test_meta/*.json +/- 1us. --- src/borg/helpers/time.py | 15 ++++++++++++--- src/borg/testsuite/archiver/repo12.tar.gz | Bin 7499 -> 7554 bytes 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/borg/helpers/time.py b/src/borg/helpers/time.py index 7dfe0d38c4..87852b32e0 100644 --- a/src/borg/helpers/time.py +++ b/src/borg/helpers/time.py @@ -26,12 +26,21 @@ def parse_local_timestamp(timestamp, tzinfo=None): return dt +_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc) + + +def utcfromtimestampns(ts_ns: int) -> datetime: + # similar to datetime.fromtimestamp, but works with ns and avoids floating point. + # also, it would avoid an overflow on 32bit platforms with old glibc. + return _EPOCH + timedelta(microseconds=ts_ns // 1000) + + def timestamp(s): """Convert a --timestamp=s argument to a datetime object.""" try: # is it pointing to a file / directory? - ts = safe_s(os.stat(s).st_mtime) - return datetime.fromtimestamp(ts, tz=timezone.utc) + ts_ns = safe_ns(os.stat(s).st_mtime_ns) + return utcfromtimestampns(ts_ns) except OSError: # didn't work, try parsing as an ISO timestamp. if no TZ is given, we assume local timezone. return parse_local_timestamp(s) @@ -84,7 +93,7 @@ def safe_ns(ts): def safe_timestamp(item_timestamp_ns): t_ns = safe_ns(item_timestamp_ns) - return datetime.fromtimestamp(t_ns / 1e9, timezone.utc) # return tz-aware utc datetime obj + return utcfromtimestampns(t_ns) # return tz-aware utc datetime obj def format_time(ts: datetime, format_spec=""): diff --git a/src/borg/testsuite/archiver/repo12.tar.gz b/src/borg/testsuite/archiver/repo12.tar.gz index cd4a45b3f1eaa85b1679b61e85fcb018491103dd..72d97b02f7746ba02734c57fecb2ab0e8b2ff021 100644 GIT binary patch literal 7554 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 literal 7499 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 From f496497d920bc4fa2818989e1b413fc5a1f6ae5c Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 2 Mar 2026 23:03:44 +0100 Subject: [PATCH 2/4] time calculations: avoid floating point Do not use 1e9 if it is not mean to be a fp calculation anyway, but rather use 10**9 for pure integer calculations. --- src/borg/testsuite/archiver/create_cmd_test.py | 2 +- src/borg/testsuite/archiver/extract_cmd_test.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/borg/testsuite/archiver/create_cmd_test.py b/src/borg/testsuite/archiver/create_cmd_test.py index 00edc0935d..f99b187a6f 100644 --- a/src/borg/testsuite/archiver/create_cmd_test.py +++ b/src/borg/testsuite/archiver/create_cmd_test.py @@ -223,7 +223,7 @@ def test_nobirthtime(archivers, request): assert same_ts_ns(sti.st_birthtime * 1e9, birthtime * 1e9) assert same_ts_ns(sto.st_birthtime * 1e9, mtime * 1e9) assert same_ts_ns(sti.st_mtime_ns, sto.st_mtime_ns) - assert same_ts_ns(sto.st_mtime_ns, mtime * 1e9) + assert same_ts_ns(sto.st_mtime_ns, mtime * 10**9) def test_create_stdin(archivers, request): diff --git a/src/borg/testsuite/archiver/extract_cmd_test.py b/src/borg/testsuite/archiver/extract_cmd_test.py index fe41c23a00..db131a5a00 100644 --- a/src/borg/testsuite/archiver/extract_cmd_test.py +++ b/src/borg/testsuite/archiver/extract_cmd_test.py @@ -153,13 +153,13 @@ def has_noatime(some_file): sti = os.stat("input/file1") sto = os.stat("output/input/file1") assert same_ts_ns(sti.st_mtime_ns, sto.st_mtime_ns) - assert same_ts_ns(sto.st_mtime_ns, mtime * 1e9) + assert same_ts_ns(sto.st_mtime_ns, mtime * 10**9) if have_noatime: assert same_ts_ns(sti.st_atime_ns, sto.st_atime_ns) - assert same_ts_ns(sto.st_atime_ns, atime * 1e9) + assert same_ts_ns(sto.st_atime_ns, atime * 10**9) else: # it touched the input file's atime while backing it up - assert same_ts_ns(sto.st_atime_ns, atime * 1e9) + assert same_ts_ns(sto.st_atime_ns, atime * 10**9) @pytest.mark.skipif(not is_utime_fully_supported(), reason="cannot setup and execute test without utime") @@ -179,7 +179,7 @@ def test_birthtime(archivers, request): assert same_ts_ns(sti.st_birthtime * 1e9, sto.st_birthtime * 1e9) assert same_ts_ns(sto.st_birthtime * 1e9, birthtime * 1e9) assert same_ts_ns(sti.st_mtime_ns, sto.st_mtime_ns) - assert same_ts_ns(sto.st_mtime_ns, mtime * 1e9) + assert same_ts_ns(sto.st_mtime_ns, mtime * 10**9) @pytest.mark.skipif(is_win32, reason="frequent test failures on github CI on win32") From eca66a0e3ec63dd481c70af64181c42ae6a6f689 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 2 Mar 2026 23:06:29 +0100 Subject: [PATCH 3/4] y2038: SUPPORT_32BIT_PLATFORMS = False, fixes #9429 Not as bad as it sounds: 32bit platforms with 64bit time_t will still work. As of 2026, this is pretty much any platform that can run borg reasonably well. --- src/borg/helpers/time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/borg/helpers/time.py b/src/borg/helpers/time.py index 87852b32e0..b98d7fe1f4 100644 --- a/src/borg/helpers/time.py +++ b/src/borg/helpers/time.py @@ -53,7 +53,7 @@ def timestamp(s): # As long as people are using borg on 32bit platforms to access borg archives, we must # keep this value True. But we can expect that we can stop supporting 32bit platforms # well before coming close to the year 2038, so this will never be a practical problem. -SUPPORT_32BIT_PLATFORMS = True # set this to False before y2038. +SUPPORT_32BIT_PLATFORMS = False # set this to False before y2038. if SUPPORT_32BIT_PLATFORMS: # second timestamps will fit into a signed int32 (platform time_t limit). From 7b69bed8c4b0c2147dd4556ebd8ef9d2da77eabb Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 2 Mar 2026 23:34:27 +0100 Subject: [PATCH 4/4] add a borg create/extract timestamp test for y2261. in y2262, nanoseconds as signed int64 will overflow. --- src/borg/testsuite/archiver/extract_cmd_test.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/borg/testsuite/archiver/extract_cmd_test.py b/src/borg/testsuite/archiver/extract_cmd_test.py index db131a5a00..2b89db1cbc 100644 --- a/src/borg/testsuite/archiver/extract_cmd_test.py +++ b/src/borg/testsuite/archiver/extract_cmd_test.py @@ -825,3 +825,19 @@ def test_extract_existing_directory(archivers, request): cmd(archiver, "extract", "test") st2 = os.stat("input/dir") assert st1.st_ino == st2.st_ino + + +@pytest.mark.skipif(not is_utime_fully_supported(), reason="cannot properly setup and execute test without utime") +def test_extract_y2261(archivers, request): + # test if roundtripping of timestamps well beyond y2038 works + archiver = request.getfixturevalue(archivers) + create_regular_file(archiver.input_path, "file_y2261", contents=b"post y2038 test") + # 2261-01-01 00:00:00 UTC as a Unix timestamp (seconds). + time_y2261 = 9183110400 + os.utime("input/file_y2261", (time_y2261, time_y2261)) + cmd(archiver, "repo-create", RK_ENCRYPTION) + cmd(archiver, "create", "test", "input") + with changedir("output"): + cmd(archiver, "extract", "test") + sto = os.stat("output/input/file_y2261") + assert same_ts_ns(sto.st_mtime_ns, time_y2261 * 10**9)