borgbackup / borgbackup/borg

`borg2 prune`: Make time spans calendar-aware

Open
#9,929 0 comments 0 reactions 0 assignees View on GitHub
cmd: prune
Dominant language
Python
Stars
13.7k
Forks
875
Avg merge
12h 37m
Merged PRs (30d)
199

Description

Thanks to @Goddesen's amazing work with #8775 (❤️), `borg2 prune` now supports interval-based retention policies like `--keep-daily 1m` to keep daily archives within a one-month time span.

As discussed earlier in https://github.com/borgbackup/borg/pull/8775#discussion_r3389964169, `borg2 prune` uses a rather unusual definition of what a month and a year is. A month is treated as a fixed 31-day interval regardless of the actual calendar month, and a year as a fixed 365-day interval regardless of leap years. This yields rather surprising results: Assume today is 2026-07-22. A timespan of `12m` results in a 372-day period (i.e. 2025-07-15), instead of the expected 2025-07-22. A timespan of `3y` results in a 3 × 365 = 1,095-day period, which introduces a one-day offset because 2024 is a leap year. So instead of the expected 2023-07-22, Borg assumes 2023-07-23.

We initially assumed that this `1m == 31d` definition wasn't new (https://github.com/borgbackup/borg/pull/8775#discussion_r3409404045), but after digging into Borg's code, it turns out that it actually is. Although [`helpers.parseformat.interval()`](https://github.com/borgbackup/borg/blob/2079b6786e2fe4e43639f68a629f463382c74705/src/borg/helpers/parseformat.py#L130-L169) already existed before, it appears that it was dead code and wasn't used anywhere before. Other Borg code that utilises relative time spans, such as `borg2 repo-list --oldest/--newest/--older/--newer`, doesn't use `helpers.parseformat.interval()`, but [`helpers.time.calculate_relative_offset()`](https://github.com/borgbackup/borg/blob/2079b6786e2fe4e43639f68a629f463382c74705/src/borg/helpers/time.py#L125-L159) instead (via [`manifest.filter_archives_by_date()`](https://github.com/borgbackup/borg/blob/2079b6786e2fe4e43639f68a629f463382c74705/src/borg/manifest.py#L51-L77), initial implementation in #7272), which interprets months and years as calendar-aware units. Since we have added a note regarding this `1m == 31d` semantic to the [docs](https://github.com/borgbackup/borg/blob/2079b6786e2fe4e43639f68a629f463382c74705/docs/usage/general/date-time.rst.inc), it now matches `borg2 prune`, but is now incorrect for the rest of Borg using relative time spans.

I'd like to suggest using `helpers.time.calculate_relative_offset()` for `borg2 prune` as well. For `borg2 prune`, `from_ts` would naturally be the value of `--from`. For the `argparse` validator one might use [`helpers.parseformat.relative_time_marker_validator()`](https://github.com/borgbackup/borg/blob/2079b6786e2fe4e43639f68a629f463382c74705/src/borg/helpers/parseformat.py#L810-L816). Might be worth removing the `helpers.parseformat.interval()` function altogether then.

Since changing this later would be a breaking change, it should ideally be addressed during the Borg 2 beta phase.

Contributor guide

Open the contributing guide

Research direction

Start with helpers.parseformat.interval(), helpers.time.calculate_relative_offset(), and manifest.filter_archives_by_date(), then trace how borg2 prune parses --keep-* values and --from. Compare the existing interval behavior with the calendar-aware path, including the relative_time_marker_validator() entry point. Done means prune applies calendar-aware month and year spans consistently and obsolete interval handling is addressed without leaving conflicting documentation.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.