@@ -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 """
0 commit comments