File Coverage

blib/lib/DateTime/Format/Text.pm
Criterion Covered Total %
statement 79 94 84.0
branch 53 70 75.7
condition 16 39 41.0
subroutine 8 8 100.0
pod 3 3 100.0
total 159 214 74.3


line stmt bran cond sub pod time code
1             package DateTime::Format::Text;
2              
3 4     4   469183 use strict;
  4         29  
  4         118  
4 4     4   22 use warnings;
  4         8  
  4         97  
5 4     4   3756 use DateTime;
  4         2181273  
  4         208  
6 4     4   42 use Carp;
  4         10  
  4         6180  
7              
8             =head1 NAME
9              
10             DateTime::Format::Text - Find a Date in Text
11              
12             =head1 VERSION
13              
14             Version 0.05
15              
16             =cut
17              
18             our $VERSION = '0.05';
19              
20             our @month_names = (
21             'january',
22             'february',
23             'march',
24             'april',
25             'may',
26             'june',
27             'july',
28             'august',
29             'september',
30             'october',
31             'november',
32             'december'
33             );
34              
35             our @day_names = (
36             'monday',
37             'tuesday',
38             'wednesday',
39             'thursday',
40             'friday',
41             'saturday',
42             'sunday'
43             );
44              
45             our @ordinal_number = ('st', 'nd', 'rd', 'th');
46             our @short_month_names = map { _shorten($_) } @month_names;
47             our @short_day_names = map { _shorten($_) } @day_names;
48              
49             our $d = join('|', @day_names);
50             our $sd = join('|', @short_day_names);
51             our $o = join('|', @ordinal_number);
52             our $m = join('|', @month_names);
53             our $sm = join('|', @short_month_names);
54              
55             sub _shorten {
56 76     76   134 return substr(shift, 0, 3);
57             };
58              
59             =head1 SYNOPSIS
60              
61             Find a date in any text.
62              
63             use DateTime::Format::Text;
64             my $dft = DateTime::Format::Text->new();
65             # ...
66              
67             =head1 SUBROUTINES/METHODS
68              
69             =head2 new
70              
71             Creates a DateTime::Format::Text object.
72             Takes no arguments
73              
74             =cut
75              
76             sub new {
77 20     20 1 864 my $proto = shift;
78 20   100     83 my $class = ref($proto) || $proto;
79              
80 20 100       61 if(!defined($class)) {
    50          
81             # Using DateTime::Format->new(), not DateTime::Format()
82             # carp(__PACKAGE__, ' use ->new() not ::new() to instantiate');
83             # return;
84              
85             # FIXME: this only works when no arguments are given
86 1         4 return bless { }, __PACKAGE__;
87             } elsif(ref($class)) {
88             # clone the given object
89 0         0 return bless { }, ref($class);
90             }
91 19         80 return bless { }, $class;
92             }
93              
94             =head2 parse_datetime
95              
96             Synonym for parse().
97              
98             =cut
99              
100             sub parse_datetime {
101 44     44 1 47834 my $self = shift;
102              
103 44 100       202 if(!ref($self)) {
    100          
    50          
104 8 100       27 if(scalar(@_)) {
105 7         19 return(__PACKAGE__->new()->parse(@_));
106             }
107 1         4 return(__PACKAGE__->new()->parse($self));
108             } elsif(ref($self) eq 'HASH') {
109 1         5 return(__PACKAGE__->new()->parse($self));
110             } elsif(ref($_[0])) {
111 0         0 Carp::croak('Usage: ', __PACKAGE__, '::parse_datetime(string => $string)');
112             }
113              
114 35         99 return $self->parse(@_);
115             }
116              
117             =head2 parse
118              
119             Returns a L<DateTime> object constructed from a date/time string embedded in
120             arbitrary text.
121              
122             Can be called as a class or object method.
123              
124             When called in an array context, returns an array containing all of the matches
125              
126             =cut
127              
128             sub parse {
129 147     147 1 21090 my $self = shift;
130 147         241 my %params;
131              
132 147 100 66     929 if(!ref($self)) {
    100          
    100          
    50          
    100          
133 5 100       20 if(scalar(@_)) {
    50          
134 4         12 return(__PACKAGE__->new()->parse(@_));
135             } elsif($self eq __PACKAGE__) {
136             # Date::Time::Format->parse()
137 0         0 Carp::croak('Usage: ', $self, '::parse(string => $string)');
138             }
139 1         7 return(__PACKAGE__->new()->parse($self));
140             } elsif(ref($self) eq 'HASH') {
141 1         5 return(__PACKAGE__->new()->parse($self));
142             } elsif(ref($_[0]) eq 'HASH') {
143 5         9 %params = %{$_[0]};
  5         21  
144             } elsif(ref($_[0])) {
145 0         0 Carp::croak('Usage: ', __PACKAGE__, '::parse(string => $string)');
146             } elsif(scalar(@_) && (scalar(@_) % 2 == 0)) {
147 4         13 %params = @_;
148             } else {
149 132         324 $params{'string'} = shift;
150             }
151              
152 141 50       656 if(my $string = $params{'string'}) {
153             # Allow the text to be an object
154 141 50       274 if(ref($string)) {
155 0         0 $string = $string->as_string();
156             }
157              
158 141 100       304 if(wantarray) {
159             # Return an array with all of the dates which match
160 61         88 my @rc;
161              
162             # Ensure that the result includes the dates in the
163             # same order that they are in the string
164 61         687 while($string =~ /(^|\D)([0-9]?[0-9])[\.\-\/ ]+?([0-1]?[0-9])[\.\-\/ ]+?([0-9]{2,4})/g) {
165             # Match dates: 01/01/2012 or 30-12-11 or 1 2 1985
166 16         156 $rc[pos $string] = $self->parse("$2 $3 $4");
167             }
168 61         6075 while($string =~ /($d|$sd)[\s,\-_\/]*?(\d?\d)[,\-\/]*($o)?[\s,\-\/]*($m|$sm)[\s,\-\/]+(\d{4})/ig) {
169             # Match dates: Sunday 1st March 2015; Sunday, 1 March 2015; Sun 1 Mar 2015; Sun-1-March-2015
170 25         141 $rc[pos $string] = $self->parse("$2 $4 $5");
171             }
172 61         14886 while($string =~ /(\d{1,2})\s($m|$sm)\s(\d{4})/ig) {
173 27         1164 $rc[pos $string] = $self->parse("$1 $2 $3");
174             }
175 61         8010 while($string =~ /($m|$sm)[\s,\-_\/]*?(\d?\d)[,\-\/]*($o)?[\s,\-\/]+(\d{4})/ig) {
176 12         374 $rc[pos $string] = $self->parse("$1 $2 $4");
177             }
178 61 100       3516 if(scalar(@rc)) {
179             # Remove empty items and create a well-ordered
180             # array to return
181 60         119 return grep { defined($_) } @rc;
  1285         2381  
182             }
183             }
184              
185             # !wantarray
186 81         201 my $day;
187             my $month;
188 81         0 my $year;
189              
190 81 100       1573 if($string =~ /(^|\D)([0-9]?[0-9])[\.\-\/ ]+?([0-1]?[0-9])[\.\-\/ ]+?([0-9]{2,4})/) {
    50          
    100          
191             # Match dates: 01/01/2012 or 30-12-11 or 1 2 1985
192 16         35 $day = $2;
193 16         30 $month = $3;
194 16         24 $year = $4;
195             } elsif($string =~ /($d|$sd)[\s,\-_\/]*?(\d?\d)[,\-\/]*($o)?[\s,\-\/]*($m|$sm)[\s,\-\/]+(\d{4})/i) {
196             # Match dates: Sunday 1st March 2015; Sunday, 1 March 2015; Sun 1 Mar 2015; Sun-1-March-2015
197 0   0     0 $day //= $2;
198 0   0     0 $month //= $4;
199 0   0     0 $year //= $5;
200             } elsif($string =~ /($m|$sm)[\s,\-_\/]*?(\d?\d)[,\-\/]*($o)?[\s,\-\/]+(\d{4})/i) {
201 12   33     70 $month //= $1;
202 12   33     42 $day //= $2;
203 12   33     38 $year //= $4;
204             # } elsif($string =~ /[^\s,\(](\d{1,2})\s+($m|$sm)[\s,]+(\d{4})/i) {
205             # # 12 September 1856
206             # $day = $1;
207             # $month = $2;
208             # $year = $3;
209             }
210              
211 81 100       221 if(!defined($month)) {
212             # Match month name
213 53 50       375 if($string =~ /($m|$sm)/i) {
214 53         154 $month = $1;
215             }
216             }
217              
218 81 100       180 if(!defined($year)) {
219             # Match Year if not already set
220 53 50       193 if($string =~ /(\d{4})/) {
221 53         123 $year = $1;
222             }
223             }
224              
225             # We've managed to dig out a month and year, is there anything that looks like a day?
226 81 100 33     365 if(defined($month) && defined($year) && !defined($day)) {
      66        
227             # Match "Sunday 1st"
228 53 50       3326 if($string =~ /($d|$sd)[,\s\-\/]+(\d?\d)[,\-\/]*($o)\s+$year/i) {
    50          
    100          
    50          
    0          
    0          
229 0         0 $day = $1;
230             } elsif($string =~ /[\s\(](\d{1,2})\s+($m|$sm)/i) {
231 0         0 $day = $1;
232             } elsif($string =~ /^(\d{1,2})\s+($m|$sm)\s/i) {
233 52         161 $day = $1;
234             } elsif($string =~ /\s(\d{1,2})th\s/) {
235 1         6 $day = $1;
236             } elsif($string =~ /\s1st\s/i) {
237 0         0 $day = 1;
238             } elsif($string =~ /\s2nd\s/i) {
239 0         0 $day = 2;
240             }
241             }
242              
243 81 50 33     376 if($day && $month && $year) {
      33        
244 81 100       241 if($year < 100) {
245 16         48 $year += 2000;
246             }
247 81         161 $month = lc($month);
248 81 100       222 if($month =~ /[a-z]/i) {
249 65         138 foreach my $i(0..11) {
250 228 100 100     684 if(($month eq $month_names[$i]) || ($month eq $short_month_names[$i])) {
251 65         259 return DateTime->new(day => $day, month => $i + 1, year => $year);
252             }
253             }
254 0         0 Carp::croak(__PACKAGE__, ": unknown month $month");
255 0         0 return;
256             } else {
257 16         63 return DateTime->new(day => $day, month => $month, year => $year);
258             }
259             }
260             } else {
261 0           Carp::croak('Usage: ', __PACKAGE__, '::parse(string => $string)');
262             }
263             }
264              
265             =head1 AUTHOR
266              
267             Nigel Horne, C<< <njh at bandsman.co.uk> >>
268              
269             Based on L<https://github.com/etiennetremel/PHP-Find-Date-in-String>.
270             Here's the author information from that:
271              
272             author Etienne Tremel
273             license L<https://creativecommons.org/licenses/by/3.0/> CC by 3.0
274             link L<http://www.etiennetremel.net>
275             version 0.2.0
276              
277             =head1 BUGS
278              
279             =head1 SEE ALSO
280              
281             L<DateTime::Format::Natural>
282              
283             =head1 SUPPORT
284              
285             You can find documentation for this module with the perldoc command.
286              
287             perldoc DateTime::Format::Text
288              
289             You can also look for information at:
290              
291             =over 4
292              
293             =item * RT: CPAN's request tracker
294              
295             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=DateTime-Format-Text>
296              
297             =item * Search CPAN
298              
299             L<http://search.cpan.org/dist/DateTime-Format-Text/>
300              
301             =back
302              
303             =head1 LICENSE AND COPYRIGHT
304              
305             Copyright 2019-2023 Nigel Horne.
306              
307             This program is released under the following licence: GPL2
308              
309             =cut
310              
311             1;