File Coverage

blib/lib/Filename/Timestamp.pm
Criterion Covered Total %
statement 14 49 28.5
branch 0 18 0.0
condition n/a
subroutine 5 6 83.3
pod 1 1 100.0
total 20 74 27.0


line stmt bran cond sub pod time code
1             package Filename::Timestamp;
2              
3 1     1   365524 use 5.010001;
  1         5  
4 1     1   6 use strict;
  1         2  
  1         37  
5 1     1   7 use warnings;
  1         1  
  1         78  
6              
7 1     1   5 use Exporter 'import';
  1         2  
  1         48  
8 1     1   626 use Time::Local qw(timelocal_posix timegm_posix);
  1         2321  
  1         1240  
9              
10             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
11             our $DATE = '2025-10-20'; # DATE
12             our $DIST = 'Filename-Timestamp'; # DIST
13             our $VERSION = '0.003'; # VERSION
14              
15             our @EXPORT_OK = qw(extract_timestamp_from_filename);
16              
17             our %SPEC;
18              
19             $SPEC{extract_timestamp_from_filename} = {
20             v => 1.1,
21             summary => 'Extract date/timestamp from filename, if any',
22             description => <<'MARKDOWN',
23              
24             *Handling of timezone.* If timestmap contains timezone indication, e.g. `+0700`,
25             will return `tz_offset` key in the result hash as well as `epoch`. Otherwise,
26             will return `tz` key in the result hash with the value of `floating` and
27             `epoch_local` calculated using 's `timelocal_posix` as well as
28             `epoch_utc` calculated using `timegm_posix`.
29              
30             MARKDOWN
31             args => {
32             filename => {
33             schema => 'str*',
34             req => 1,
35             pos => 0,
36             },
37             all => {
38             schema => 'bool',
39             summary => 'Find all timestamps instead of the first found only',
40             description => <<'MARKDOWN',
41              
42             Not yet implemented.
43              
44             MARKDOWN
45             },
46             },
47             result_naked => 1,
48             result => {
49             schema => ['any*', of=>['bool*', 'hash*']],
50             description => <<'MARKDOWN',
51              
52             Return false if no timestamp is detected. Otherwise return a hash of
53             information, which contains these keys: `epoch`, `year`, `month`, `day`, `hour`,
54             `minute`, `second`.
55              
56             MARKDOWN
57             },
58             examples => [
59             {
60             args => {filename=>'2024-09-08T12_35_48+07_00.JPEG'},
61             },
62             {
63             args => {filename=>'IMG_20240908_095444.jpg'},
64             },
65             {
66             args => {filename=>'VID_20240908_092426.mp4'},
67             },
68             {
69             args => {filename=>'Screenshot_2024-09-01-11-40-44-612_org.mozilla.firefox.jpg'},
70             },
71             {
72             args => {filename=>'IMG-20241204-WA0001.jpg'},
73             },
74             {
75             args => {filename=>'foo.txt'},
76             },
77             ],
78             };
79             sub extract_timestamp_from_filename {
80 0     0 1   my %args = @_;
81              
82 0           my $filename = $args{filename};
83              
84 0           my $res = {};
85 0 0         if ($filename =~ /(\d{4})[_.-](\d{2})[_.-](\d{2}) # 1 (year), 2 (mon), 3 (day)
    0          
86             (?:
87             [T-]
88             (\d{2})[_.-](\d{2})[_.-](\d{2}) # 4 (hour), 5 (minute), 6 (second)
89             (?:
90             ([+-])(\d{2})[_-]?(\d{2}) # 7 (timestamp sign), 8 (timestamp hour), 9 (timestamp minute)
91             )?
92             )?
93             /x) {
94 0           $res->{year} = $1+0;
95 0           $res->{month} = $2+0;
96 0           $res->{day} = $3+0;
97 0 0         if (defined $4) {
98 0           $res->{hour} = $4+0;
99 0           $res->{minute} = $5+0;
100 0           $res->{second} = $6+0;
101             } else {
102 0           $res->{hour} = 0;
103 0           $res->{minute} = 0;
104 0           $res->{second} = 0;
105             }
106 0 0         if ($7) {
107 0 0         $res->{tz_offset} = ($7 eq '+' ? 1:-1) * $8*3600 + $9*60;
108             }
109             } elsif ($filename =~ /(\d{4})(\d{2})(\d{2}) # 1 (year), 2 (mon), 3 (day)
110             (?:
111             [_-]
112             (\d{2})(\d{2})(\d{2}) # 4 (hour), 5 (min), 6 (second)
113             (?:
114             ([+-])(\d{2})[_-]?(\d{2}) # 7 (timestamp sign), 8 (timestamp hour), 9 (timestamp minute)
115             )?
116             )?
117             /x) {
118 0           $res->{year} = $1+0;
119 0           $res->{month} = $2+0;
120 0           $res->{day} = $3+0;
121 0 0         if (defined $4) {
122 0           $res->{hour} = $4+0;
123 0           $res->{minute} = $5+0;
124 0           $res->{second} = $6+0;
125             } else {
126 0           $res->{hour} = 0;
127 0           $res->{minute} = 0;
128 0           $res->{second} = 0;
129             }
130 0 0         if ($7) {
131 0 0         $res->{tz_offset} = ($7 eq '+' ? 1:-1) * $8*3600 + $9*60;
132             }
133             } else {
134 0           return 0;
135             }
136              
137 0 0         if (defined $res->{tz_offset}) {
138             $res->{epoch} = timegm_posix(
139             $res->{second},
140             $res->{minute},
141             $res->{hour},
142             $res->{day},
143             $res->{month} - 1,
144             $res->{year} - 1900,
145 0           ) + $res->{tz_offset};
146             } else {
147             $res->{epoch_local} = $res->{epoch} = timelocal_posix(
148             $res->{second},
149             $res->{minute},
150             $res->{hour},
151             $res->{day},
152             $res->{month} - 1,
153 0           $res->{year} - 1900,
154             );
155 0           $res->{tz} = 'floating';
156             $res->{epoch_utc} = timegm_posix(
157             $res->{second},
158             $res->{minute},
159             $res->{hour},
160             $res->{day},
161             $res->{month} - 1,
162 0           $res->{year} - 1900,
163             );
164             }
165              
166 0           $res;
167             }
168              
169             1;
170             # ABSTRACT: Extract date/timestamp from filename, if any
171              
172             __END__