@@ -247,6 +247,8 @@ fn metadata_for(path: &Path) -> Metadata {
247247 lazy_static ! {
248248 static ref DATE_TIME_PATTERN : Regex =
249249 Regex :: new( r".*(?:(?:img)|(?:vid))[-_](\d{4})(\d{2})(\d{2})[-_].*" ) . unwrap( ) ;
250+ static ref UNIX_EPOCH_PATTERN : Regex =
251+ Regex :: new( r".*(?:(?:img)|(?:vid))[-_](\d{13})\..*" ) . unwrap( ) ;
250252 }
251253
252254 fn validate ( datetime : DateTime < Utc > ) -> Option < DateTime < Utc > > {
@@ -279,6 +281,21 @@ fn metadata_for(path: &Path) -> Metadata {
279281 . and_then ( |string| string. parse ( ) . ok ( ) )
280282 } )
281283 . and_then ( validate)
284+ . or_else ( || {
285+ UNIX_EPOCH_PATTERN . captures ( & lowercase) . and_then ( |c| {
286+ let millis = c[ 1 ] . parse :: < i64 > ( ) . ok ( ) ?;
287+ Some ( DateTime :: < Utc > :: from_naive_utc_and_offset (
288+ DateTime :: from_timestamp (
289+ millis / 1000 ,
290+ u32:: try_from ( ( millis % 1000 ) * 1_000_000 ) . unwrap ( ) ,
291+ )
292+ . unwrap ( )
293+ . naive_utc ( ) ,
294+ Utc ,
295+ ) )
296+ } )
297+ } )
298+ . and_then ( validate)
282299 . unwrap_or ( * DEFAULT_DATETIME ) ;
283300
284301 metadata
@@ -940,3 +957,26 @@ pub async fn mark_bad(conn: &AsyncMutex<SqliteConnection>, hash: &str) -> Result
940957 } )
941958 . await
942959}
960+
961+ #[ cfg( test) ]
962+ mod test {
963+ use super :: * ;
964+
965+ #[ test]
966+ fn metadata_for_whatsapp_file ( ) -> Result < ( ) > {
967+ assert_eq ! (
968+ metadata_for( & Path :: new( "/does-not-exist/IMG-20210515-WA0004.jpg" ) ) . datetime,
969+ "2021-05-15T00:00:00Z" . parse:: <DateTime <Utc >>( ) ?
970+ ) ;
971+ Ok ( ( ) )
972+ }
973+
974+ #[ test]
975+ fn metadata_for_kraken_sports_file ( ) -> Result < ( ) > {
976+ assert_eq ! (
977+ metadata_for( & Path :: new( "/does-not-exist/IMG-1748014806987.jpg" ) ) . datetime,
978+ "2025-05-23T15:40:06.987Z" . parse:: <DateTime <Utc >>( ) ?
979+ ) ;
980+ Ok ( ( ) )
981+ }
982+ }
0 commit comments