Merge pull request #9430 from ThomasWaldmann/y2038

Y2038+
This commit is contained in:
TW 2026-03-04 14:16:49 +01:00 committed by GitHub
commit 1c05ab32db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 34 additions and 9 deletions

View file

@ -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)
@ -44,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).
@ -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=""):

View file

@ -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):

View file

@ -153,13 +153,13 @@ def test_atime(archivers, request):
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")
@ -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)