Skip to content

Commit 1060e30

Browse files
author
Roland Sommer
committed
Allow unixtime-style timestamp extraction
Some backup solutions prefer using UNIX timestamps for tagging backup files or directories. In order to enable the rotation of such files/directories some extra handling is added to enable scanning for UNIX timestamps in file- or directory-names.
1 parent 32e966c commit 1060e30

File tree

3 files changed

+51
-18
lines changed

3 files changed

+51
-18
lines changed

README.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,9 @@ intended you have no right to complain ;-).
155155
usage of the ``-H``, ``--hourly`` option for details about ``COUNT``."
156156
"``-t``, ``--timestamp-pattern=PATTERN``","Customize the regular expression pattern that is used to match and extract
157157
timestamps from filenames. ``PATTERN`` is expected to be a Python compatible
158-
regular expression that must define the named capture groups 'year',
159-
'month' and 'day' and may define 'hour', 'minute' and 'second'."
158+
regular expression that must define a named capture group 'unixtime' or the
159+
named capture groups 'year', 'month' and 'day' and may define 'hour',
160+
'minute' and 'second'."
160161
"``-I``, ``--include=PATTERN``","Only process backups that match the shell pattern given by ``PATTERN``. This
161162
argument can be repeated. Make sure to quote ``PATTERN`` so the shell doesn't
162163
expand the pattern before it's received by rotate-backups."

rotate_backups/__init__.py

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,16 @@ def strict(self):
447447
"""
448448
return True
449449

450+
@mutable_property
451+
def _is_unixtime(self):
452+
"""
453+
Is the given pattern used to extract a unix timestamp?
454+
455+
This private property reflects if the given regex is used to exctract
456+
a unix timestamp from file- or directorynames.
457+
"""
458+
return False
459+
450460
@mutable_property
451461
def timestamp_pattern(self):
452462
"""
@@ -458,8 +468,9 @@ def timestamp_pattern(self):
458468
:func:`re.compile()` documentation for details).
459469
460470
The regular expression pattern is expected to be a Python compatible
461-
regular expression that defines the named capture groups 'year',
462-
'month' and 'day' and optionally 'hour', 'minute' and 'second'.
471+
regular expression that defines the named capture group 'unixtime' or
472+
the named capture groups 'year', 'month' and 'day' and optionally
473+
'hour', 'minute' and 'second'.
463474
464475
String values are automatically coerced to compiled regular expressions
465476
by calling :func:`~humanfriendly.coerce_pattern()`, in this case only
@@ -476,10 +487,15 @@ def timestamp_pattern(self):
476487
def timestamp_pattern(self, value):
477488
"""Coerce the value of :attr:`timestamp_pattern` to a compiled regular expression."""
478489
pattern = coerce_pattern(value, re.VERBOSE)
479-
for component, required in SUPPORTED_DATE_COMPONENTS:
480-
if component not in pattern.groupindex and required:
481-
raise ValueError("Pattern is missing required capture group! (%s)" % component)
482-
set_property(self, 'timestamp_pattern', pattern)
490+
if "unixtime" in pattern.groupindex:
491+
set_property(self, 'timestamp_pattern', pattern)
492+
self._is_unixtime = True
493+
else:
494+
for component, required in SUPPORTED_DATE_COMPONENTS:
495+
if component not in pattern.groupindex and required:
496+
raise ValueError("Pattern is missing required capture group! (%s)" % component)
497+
set_property(self, 'timestamp_pattern', pattern)
498+
self._is_unixtime = False
483499

484500
def rotate_concurrent(self, *locations, **kw):
485501
"""
@@ -678,15 +694,30 @@ def match_to_datetime(self, match):
678694
"""
679695
kw = {}
680696
captures = match.groupdict()
681-
for component, required in SUPPORTED_DATE_COMPONENTS:
682-
value = captures.get(component)
683-
if value:
684-
kw[component] = int(value, 10)
685-
elif required:
686-
raise ValueError("Missing required date component! (%s)" % component)
697+
if self._is_unixtime:
698+
base = int(match.groupdict().get("unixtime"))
699+
# Try seconds- and milliseconds-precision timestamps.
700+
for value in (base, base / 1000):
701+
try:
702+
timestamp = datetime.datetime.fromtimestamp(value)
703+
break
704+
except ValueError:
705+
timestamp = None
706+
if timestamp is None:
707+
logger.notice("Ignoring %s due to invalid date (%s).", value, match.group())
687708
else:
688-
kw[component] = 0
689-
return datetime.datetime(**kw)
709+
logger.verbose("Extracted timestamp %r from %r", timestamp, value)
710+
return timestamp
711+
else:
712+
for component, required in SUPPORTED_DATE_COMPONENTS:
713+
value = captures.get(component)
714+
if value:
715+
kw[component] = int(value, 10)
716+
elif required:
717+
raise ValueError("Missing required date component! (%s)" % component)
718+
else:
719+
kw[component] = 0
720+
return datetime.datetime(**kw)
690721

691722
def group_backups(self, backups):
692723
"""

rotate_backups/cli.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@
7373
7474
Customize the regular expression pattern that is used to match and extract
7575
timestamps from filenames. PATTERN is expected to be a Python compatible
76-
regular expression that must define the named capture groups 'year',
77-
'month' and 'day' and may define 'hour', 'minute' and 'second'.
76+
regular expression that must define the named capture group 'unixtime' or
77+
the named capture groups 'year', 'month' and 'day' and may define 'hour',
78+
'minute' and 'second'.
7879
7980
-I, --include=PATTERN
8081

0 commit comments

Comments
 (0)