File Coverage

blib/lib/DateTime/Format/Text.pm
Criterion Covered Total %
statement 79 92 85.8
branch 53 70 75.7
condition 16 39 41.0
subroutine 8 8 100.0
pod 3 3 100.0
total 159 212 75.0


line stmt bran cond sub pod time code
1             package DateTime::Format::Text;
2              
3 4     4   466447 use strict;
  4         27  
  4         112  
4 4     4   20 use warnings;
  4         11  
  4         100  
5 4     4   4290 use DateTime;
  4         2145265  
  4         214  
6 4     4   44 use Carp;
  4         10  
  4         6047  
7              
8             =head1 NAME
9              
10             DateTime::Format::Text - Find a Date in Text
11              
12             =head1 VERSION
13              
14             Version 0.04
15              
16             =cut
17              
18             our $VERSION = '0.04';
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 { _shortenize($_) } @month_names;
47             our @short_day_names = map { _shortenize($_) } @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 _shortenize {
56 76     76   159 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 19     19 1 971 my $proto = shift;
78 19   100     76 my $class = ref($proto) || $proto;
79              
80 19 100       57 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         5 return bless { }, __PACKAGE__;
87             } elsif(ref($class)) {
88             # clone the given object
89 0         0 return bless { }, ref($class);
90             }
91 18         78 return bless { }, $class;
92             }
93              
94             =head2 parse_datetime
95              
96             Synonym for parse().
97              
98             =cut
99              
100             sub parse_datetime {
101 43     43 1 45235 my $self = shift;
102              
103 43 100       198 if(!ref($self)) {
    100          
    50          
104 7 100       21 if(scalar(@_)) {
105 6         19 return(__PACKAGE__->new()->parse(@_));
106             }
107 1         5 return(__PACKAGE__->new()->parse($self));
108             } elsif(ref($self) eq 'HASH') {
109 1         6 return(__PACKAGE__->new()->parse($self));
110             } elsif(ref($_[0])) {
111 0         0 Carp::croak('Usage: ', __PACKAGE__, '::parse_datetime(string => $string)');
112             }
113              
114 35         94 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 144     144 1 23543 my $self = shift;
130 144         254 my %params;
131              
132 144 100 66     878 if(!ref($self)) {
    100          
    100          
    50          
    100          
133 5 100       17 if(scalar(@_)) {
    50          
134 4         11 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         6 %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         16 %params = @_;
148             } else {
149 129         303 $params{'string'} = shift;
150             }
151              
152 138 50       272 if(my $string = $params{'string'}) {
153             # Allow the text to be an object
154 138 50       270 if(ref($string)) {
155 0         0 $string = $string->as_string();
156             }
157              
158 138 100       285 if(wantarray) {
159             # Return an array with all of the dates which match
160 60         86 my @rc;
161              
162             # Ensure that the result includes the dates in the
163             # same order that they are in the string
164 60         415 while($string =~ /([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         92 $rc[pos $string] = $self->parse("$1 $2 $3");
167             }
168 60         5908 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         158 $rc[pos $string] = $self->parse("$2 $4 $5");
171             }
172 60         15682 while($string =~ /(\d{1,2})\s($m|$sm)\s(\d{4})/ig) {
173 25         917 $rc[pos $string] = $self->parse("$1 $2 $3");
174             }
175 60         8693 while($string =~ /($m|$sm)[\s,\-_\/]*?(\d?\d)[,\-\/]*($o)?[\s,\-\/]+(\d{4})/ig) {
176 12         363 $rc[pos $string] = $self->parse("$1 $2 $4");
177             }
178 60 100       3455 if(scalar(@rc)) {
179             # Remove empty items and create a well-ordered
180             # array to return
181 59         118 return grep { defined($_) } @rc;
  1227         2390  
182             }
183             }
184              
185             # !wantarray
186 79         205 my $day;
187             my $month;
188 79         0 my $year;
189              
190 79 100       1416 if($string =~ /([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         70 $day = $1;
193 16         25 $month = $2;
194 16         28 $year = $3;
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     68 $month //= $1;
202 12   33     46 $day //= $2;
203 12   33     35 $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 79 100       209 if(!defined($month)) {
212             # Match month name
213 51 50       372 if($string =~ /($m|$sm)/i) {
214 51         201 $month = $1;
215             }
216             }
217              
218 79 100       168 if(!defined($year)) {
219             # Match Year if not already set
220 51 50       163 if($string =~ /(\d{4})/) {
221 51         105 $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 79 100 33     347 if(defined($month) && defined($year) && !defined($day)) {
      66        
227             # Match "Sunday 1st"
228 51 50       3269 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 50         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 79 50 33     374 if($day && $month && $year) {
      33        
244 79 100       213 if($year < 100) {
245 16         23 $year += 2000;
246             }
247 79         167 $month = lc($month);
248 79 100       219 if($month =~ /[a-z]/i) {
249 63         160 foreach my $i(0..11) {
250 218 100 100     702 if(($month eq $month_names[$i]) || ($month eq $short_month_names[$i])) {
251 63         276 return DateTime->new(day => $day, month => $i + 1, year => $year);
252             }
253             }
254             } else {
255 16         65 return DateTime->new(day => $day, month => $month, year => $year);
256             }
257             }
258             } else {
259 0           Carp::croak('Usage: ', __PACKAGE__, '::parse(string => $string)');
260             }
261             }
262              
263             =head1 AUTHOR
264              
265             Nigel Horne, C<< <njh at bandsman.co.uk> >>
266              
267             Based on L<https://github.com/etiennetremel/PHP-Find-Date-in-String>.
268             Here's the author information from that:
269              
270             author Etienne Tremel
271             license L<https://creativecommons.org/licenses/by/3.0/> CC by 3.0
272             link L<http://www.etiennetremel.net>
273             version 0.2.0
274              
275             =head1 BUGS
276              
277             =head1 SEE ALSO
278              
279             L<DateTime::Format::Natural>
280              
281             =head1 SUPPORT
282              
283             You can find documentation for this module with the perldoc command.
284              
285             perldoc DateTime::Format::Text
286              
287             You can also look for information at:
288              
289             =over 4
290              
291             =item * RT: CPAN's request tracker
292              
293             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=DateTime-Format-Text>
294              
295             =item * Search CPAN
296              
297             L<http://search.cpan.org/dist/DateTime-Format-Text/>
298              
299             =back
300              
301             =head1 LICENSE AND COPYRIGHT
302              
303             Copyright 2019-2023 Nigel Horne.
304              
305             This program is released under the following licence: GPL2
306              
307             =cut
308              
309             1;