Skip to content

Commit adda634

Browse files
JSCU-CNISchamper
andauthored
Add support for leap days in year_rollover_helper (#1081)
Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com>
1 parent c0cae58 commit adda634

File tree

2 files changed

+41
-2
lines changed

2 files changed

+41
-2
lines changed

dissect/target/helpers/utils.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,29 @@ def year_rollover_helper(
149149
# We have to append the current_year to strptime instead of adding it using replace later.
150150
# This prevents DeprecationWarnings on cpython >= 3.13 and Exceptions on cpython >= 3.15.
151151
# See https://github.com/python/cpython/issues/70647 and https://github.com/python/cpython/pull/117107.
152-
compare_ts = datetime.strptime(f"{timestamp.group(0)};1900", f"{ts_format};%Y")
152+
# Use 1904 instead of 1900 to include leap days (29 Feb).
153+
try:
154+
compare_ts = datetime.strptime(f"{timestamp.group(0)};1904", f"{ts_format};%Y")
155+
except ValueError as e:
156+
log.warning("Unable to create comparison timestamp for %r in line %r: %s", timestamp.group(0), line, e)
157+
log.debug("", exc_info=e)
158+
continue
159+
153160
if last_seen_month and compare_ts.month > last_seen_month:
154161
current_year -= 1
155162
last_seen_month = compare_ts.month
156163

157164
try:
158165
relative_ts = datetime.strptime(f"{timestamp.group(0)};{current_year}", f"{ts_format};%Y")
159166
except ValueError as e:
160-
log.warning("Timestamp '%s' does not match format '%s', skipping line.", timestamp.group(0), ts_format)
167+
log.warning(
168+
"Timestamp '%s;%s' does not match format '%s;%%Y', skipping line %r: %s",
169+
timestamp.group(0),
170+
current_year,
171+
ts_format,
172+
line,
173+
e,
174+
)
161175
log.debug("", exc_info=e)
162176
continue
163177

tests/helpers/test_utils.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ def test_helpers_fsutil_year_rollover_helper() -> None:
5858
mocked_stat = fsutil.stat_result([stat.S_IFREG, 1337, id(vfs), 0, 0, 0, len(content), 0, 3384460800, 0])
5959
with patch.object(path, "stat", return_value=mocked_stat):
6060
result = list(utils.year_rollover_helper(path, re_ts, ts_fmt))
61+
assert len(result) == 10
62+
6163
year_line = [(ts.year, line) for ts, line in result]
6264

6365
assert result[0][0].tzinfo == datetime.timezone.utc
@@ -78,3 +80,26 @@ def test_helpers_fsutil_year_rollover_helper() -> None:
7880

7981
assert year_line[0] == (2022, "Jan 1 13:21:34 Line 10")
8082
assert year_line[-1] == (2018, "Dec 31 03:14:15 Line 1")
83+
84+
85+
def test_helpers_fsutil_year_rollover_helper_leap_day() -> None:
86+
"""test if we correctly handle leap days such as 2024-02-29."""
87+
88+
content = """
89+
Feb 28 11:00:00 Line 1
90+
Feb 29 12:00:00 Line 2
91+
Mar 1 13:00:00 Line 3
92+
"""
93+
fs = VirtualFilesystem()
94+
fs.map_file_fh("file", io.BytesIO(textwrap.dedent(content).encode()))
95+
path = fs.path("file")
96+
97+
re_ts = r"(\w+\s{1,2}\d+\s\d{2}:\d{2}:\d{2})"
98+
ts_fmt = "%b %d %H:%M:%S"
99+
mocked_stat = fsutil.stat_result(
100+
[stat.S_IFREG, 1337, id(fs), 0, 0, 0, len(content), 0, 1709294400, 0]
101+
) # mtime is set to 2024-03-01.
102+
103+
with patch.object(path, "stat", return_value=mocked_stat):
104+
result = list(utils.year_rollover_helper(path, re_ts, ts_fmt))
105+
assert len(result) == 3

0 commit comments

Comments
 (0)