Skip to content

Commit 358b367

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 358b367

File tree

3 files changed

+48
-18
lines changed

3 files changed

+48
-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: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,13 @@ 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+
return False
456+
450457
@mutable_property
451458
def timestamp_pattern(self):
452459
"""
@@ -458,8 +465,9 @@ def timestamp_pattern(self):
458465
:func:`re.compile()` documentation for details).
459466
460467
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'.
468+
regular expression that defines the named capture group 'unixtime' or
469+
the named capture groups 'year', 'month' and 'day' and optionally
470+
'hour', 'minute' and 'second'.
463471
464472
String values are automatically coerced to compiled regular expressions
465473
by calling :func:`~humanfriendly.coerce_pattern()`, in this case only
@@ -476,10 +484,15 @@ def timestamp_pattern(self):
476484
def timestamp_pattern(self, value):
477485
"""Coerce the value of :attr:`timestamp_pattern` to a compiled regular expression."""
478486
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)
487+
if "unixtime" in pattern.groupindex:
488+
set_property(self, 'timestamp_pattern', pattern)
489+
self._is_unixtime = True
490+
else:
491+
for component, required in SUPPORTED_DATE_COMPONENTS:
492+
if component not in pattern.groupindex and required:
493+
raise ValueError("Pattern is missing required capture group! (%s)" % component)
494+
set_property(self, 'timestamp_pattern', pattern)
495+
self._is_unixtime = False
483496

484497
def rotate_concurrent(self, *locations, **kw):
485498
"""
@@ -678,15 +691,30 @@ def match_to_datetime(self, match):
678691
"""
679692
kw = {}
680693
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)
694+
if self._is_unixtime:
695+
base = int(match.groupdict().get("unixtime"))
696+
# Try seconds- and milliseconds-precision timestamps.
697+
for value in (base, base / 1000):
698+
try:
699+
timestamp = datetime.datetime.fromtimestamp(value)
700+
break
701+
except ValueError:
702+
timestamp = None
703+
if timestamp is None:
704+
logger.notice("Ignoring %s due to invalid date (%s).", value, match.group())
687705
else:
688-
kw[component] = 0
689-
return datetime.datetime(**kw)
706+
logger.verbose("Extracted timestamp %r from %r", timestamp, value)
707+
return timestamp
708+
else:
709+
for component, required in SUPPORTED_DATE_COMPONENTS:
710+
value = captures.get(component)
711+
if value:
712+
kw[component] = int(value, 10)
713+
elif required:
714+
raise ValueError("Missing required date component! (%s)" % component)
715+
else:
716+
kw[component] = 0
717+
return datetime.datetime(**kw)
690718

691719
def group_backups(self, backups):
692720
"""

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)