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