File Coverage

blib/lib/WWW/Noss/Timestamp.pm
Criterion Covered Total %
statement 35 35 100.0
branch 11 16 68.7
condition 6 9 66.6
subroutine 6 6 100.0
pod 2 2 100.0
total 60 68 88.2


line stmt bran cond sub pod time code
1             package WWW::Noss::Timestamp;
2 6     6   129955 use 5.016;
  6         19  
3 6     6   30 use strict;
  6         16  
  6         127  
4 6     6   24 use warnings;
  6         50  
  6         399  
5             our $VERSION = '2.02';
6              
7 6     6   3562 use Time::Piece;
  6         77307  
  6         27  
8              
9             my %MONTHS = (
10             'jan' => '01',
11             'feb' => '02',
12             'mar' => '03',
13             'apr' => '04',
14             'may' => '05',
15             'jun' => '06',
16             'jul' => '07',
17             'aug' => '08',
18             'sep' => '09',
19             'oct' => '10',
20             'nov' => '11',
21             'dec' => '12',
22             );
23              
24             # Regex taken from the loose parser in the DateTime::Format::Mail module.
25             my $mail_rx = qr{
26             ^ \s*
27             # Optional week day name
28             (?i:
29             (?:Mon|Tue|Wed|Thu|Fri|Sat|Sun|[A-Z][a-z][a-z]) ,? # Day name + comma
30             )?
31             \s*
32             (?\d{1,2}) # Day of month
33             [-\s]*
34             (?i: (? Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ) # month
35             [-\s]*
36             (?(?:\d\d)?\d\d) # year
37             \s+
38             (?\d?\d):(?\d?\d) (?: :(?\d?\d) )? # hour:min:sec
39             # Optional time zone
40             (?:
41             \s+ "? (?
42             [+-] \d{4} # standard
43             | [A-Z]+ # obsolete (ignored)
44             | GMT [+-] \d+ # empirical (converted)
45             | [A-Z]+\d+ # wierd emprical (ignored)
46             | [a-zA-Z/]+ # linux (ignored)
47             | [+-]{0,2} \d{3,5} # corrupted standard
48             ) "?
49             )?
50             (?: \s+ \([^\)]+\) )? # friendly tz name; empirical
51             \s* \.? $
52             }x;
53              
54             # Regex adapted from DateTime::Format::RFC3339.
55             my $rfc3339_rx = qr{
56             ^
57             # yyyy-mm-dd
58             (? \d{4})-(? \d{2})-(? \d{2})
59             T # date/time seperator
60             # hh:mm:ss
61             (? \d{2}):(? \d{2}):(? \d{2})
62             # nanoseconds (ignored)
63             (?: \. \d{1,9}\d*)?
64             (?
65             Z # UTC (zulu)
66             | [+-]\d{2}:\d{2}
67             )
68             $
69             }x;
70              
71             sub mail {
72              
73 2301     2301 1 1473543 my ($class, $time) = @_;
74              
75 2301 50       73056 $time =~ $mail_rx or return undef;
76              
77 2301         28990 my $dom = sprintf "%02d", $+{ dom };
78 2301         13722 my $month = $MONTHS{ lc $+{ month } };
79             my $year =
80             length $+{ year } == 4
81             ? $+{ year }
82 2301 50       17552 : $+{ year } >= 69
    100          
83             ? "19$+{ year }"
84             : "20$+{ year }";
85 2301   50     14071 my $hour = sprintf "%02d", $+{ hour } // 0;
86 2301   50     9816 my $min = sprintf "%02d", $+{ min } // 0;
87 2301   100     10538 my $sec = sprintf "%02d", $+{ sec } // 0;
88             my $tz =
89 2301 100 66     28134 (defined $+{ tz } and $+{ tz } =~ /^([+-])(\d{4})$/)
90             ? $1 . sprintf "%04d", $2
91             : '-0000';
92              
93 2301         5740 my $tp = eval {
94 2301         13298 Time::Piece->strptime(
95             join(' ', $dom, $month, $year, $hour, $min, $sec, $tz),
96             '%d %m %Y %H %M %S %z',
97             );
98             };
99              
100 2301 50       90798 return defined $tp ? $tp->epoch : undef;
101              
102             }
103              
104             sub rfc3339 {
105              
106 3713     3713 1 2324265 my ($class, $time) = @_;
107              
108 3713 50       64113 $time =~ $rfc3339_rx or return undef;
109              
110 3713         31359 my $year = $+{ year };
111 3713         17024 my $month = $+{ month };
112 3713         14216 my $dom = $+{ dom };
113 3713         11995 my $hour = $+{ hour };
114 3713         13577 my $min = $+{ min };
115 3713         14804 my $sec = $+{ sec };
116             my $tz =
117             $+{ tz } eq 'Z'
118             ? '-0000'
119 3713 100       38186 : $+{ tz } =~ s/://gr;
120              
121 3713         10287 my $tp = eval {
122 3713         25292 Time::Piece->strptime(
123             join(' ', $year, $month, $dom, $hour, $min, $sec, $tz),
124             '%Y %m %d %H %M %S %z'
125             );
126             };
127              
128 3713 50       163013 return defined $tp ? $tp->epoch : undef;
129              
130             }
131              
132             1;
133              
134             =head1 NAME
135              
136             WWW::Noss::Timestamp - Parse timestamps
137              
138             =head1 USAGE
139              
140             use WWW::Noss::Timestamp;
141              
142             my $epoch = WWW::Noss::Timestamp->rfc3339(
143             '2025-07-12T00:23:00Z'
144             );
145              
146             =head1 DESCRIPTION
147              
148             B is a module that provides methods for parsing various
149             timestamp formats used by RSS and Atom feeds. This is a private module, please
150             consult the L manual for user documentation.
151              
152             =head1 METHODS
153              
154             Each method is invoked as a class method. Methods will return the timestamp's
155             seconds since the Unix epoch or C on failure.
156              
157             =over 4
158              
159             =item $epoch = WWW::Noss::Timestamp->mail($str)
160              
161             Parse RFC2822/822 timestamps, used by RSS feeds. This is a lenient parser that
162             is capable of parsing some non-standard timestamps.
163              
164             =item $epoch = WWW::Noss::Timestamp->rfc3339($str)
165              
166             Parse RFC3339 timestamps, used by Atom feeds.
167              
168             =back
169              
170             =head1 AUTHOR
171              
172             Written by Samuel Young, Esamyoung12788@gmail.comE.
173              
174             This project's source can be found on its
175             L. Comments and pull
176             requests are welcome!
177              
178             =head1 COPYRIGHT
179              
180             Copyright (C) 2025-2026 Samuel Young
181              
182             This program is free software: you can redistribute it and/or modify
183             it under the terms of the GNU General Public License as published by
184             the Free Software Foundation, either version 3 of the License, or
185             (at your option) any later version.
186              
187             =head1 SEE ALSO
188              
189             L
190              
191             =cut
192              
193             # vim: expandtab shiftwidth=4