File Coverage

blib/lib/DateTime/Format/Natural.pm
Criterion Covered Total %
statement 389 409 95.1
branch 121 144 84.0
condition 73 106 68.8
subroutine 60 60 100.0
pod 7 7 100.0
total 650 726 89.5


line stmt bran cond sub pod time code
1             package DateTime::Format::Natural;
2              
3 26     26   2451663 use strict;
  26         224  
  26         763  
4 26     26   138 use warnings;
  26         56  
  26         898  
5 26         13438 use base qw(
6             DateTime::Format::Natural::Calc
7             DateTime::Format::Natural::Duration
8             DateTime::Format::Natural::Expand
9             DateTime::Format::Natural::Extract
10             DateTime::Format::Natural::Formatted
11             DateTime::Format::Natural::Helpers
12             DateTime::Format::Natural::Rewrite
13 26     26   177 );
  26         65  
14 26     26   201 use boolean qw(true false);
  26         72  
  26         155  
15              
16 26     26   1752 use Carp qw(croak);
  26         68  
  26         1217  
17 26     26   182 use DateTime ();
  26         61  
  26         394  
18 26     26   12406 use DateTime::HiRes ();
  26         45733  
  26         577  
19 26     26   198 use DateTime::TimeZone ();
  26         69  
  26         548  
20 26     26   146 use List::MoreUtils qw(all any none);
  26         90  
  26         280  
21 26     26   36512 use Params::Validate ':all';
  26         77034  
  26         4936  
22 26     26   257 use Scalar::Util qw(blessed);
  26         86  
  26         1477  
23 26     26   183 use Storable qw(dclone);
  26         55  
  26         1440  
24              
25 26     26   178 use DateTime::Format::Natural::Utils qw(trim);
  26         63  
  26         157392  
26              
27             our $VERSION = '1.17_02';
28              
29             validation_options(
30             on_fail => sub
31             {
32             my ($error) = @_;
33             chomp $error;
34             croak $error;
35             },
36             stack_skip => 2,
37             );
38              
39             sub new
40             {
41 10582     10582 1 154456 my $class = shift;
42              
43 10582   33     50517 my $self = bless {}, ref($class) || $class;
44              
45 10582         41789 $self->_init_check(@_);
46 10581         897881 $self->_init(@_);
47              
48 10581         29144 return $self;
49             }
50              
51             sub _init
52             {
53 10581     10581   23265 my $self = shift;
54 10581         33037 my %opts = @_;
55              
56 10581         31514 my %presets = (
57             lang => 'en',
58             format => 'd/m/y',
59             demand_future => false,
60             prefer_future => false,
61             time_zone => 'floating',
62             );
63 10581         89046 foreach my $opt (keys %presets) {
64 52905         127256 $self->{ucfirst $opt} = $presets{$opt};
65             }
66 10581         30444 foreach my $opt (keys %opts) {
67 7004 50       18341 if (defined $opts{$opt}) {
68 7004         16116 $self->{ucfirst $opt} = $opts{$opt};
69             }
70             }
71 10581   100     51178 $self->{Daytime} = $opts{daytime} || {};
72              
73 10581         38673 my $mod = join '::', (__PACKAGE__, 'Lang', uc $self->{Lang});
74 10581 50       830233 eval "require $mod" or die $@;
75              
76 10581         67153 $self->{data} = $mod->__new();
77 10581         31291 $self->{grammar_class} = $mod;
78              
79 10581         43066 $self->{mode} = '';
80             }
81              
82             sub _init_check
83             {
84 10582     10582   18873 my $self = shift;
85              
86             validate(@_, {
87             demand_future => {
88             # SCALARREF due to boolean.pm's implementation
89             type => BOOLEAN | SCALARREF,
90             optional => true,
91             callbacks => {
92             'mutually exclusive' => sub
93             {
94 1073 100   1073   68752 return true unless exists $_[1]->{prefer_future};
95 1         10 die "prefer_future provided\n";
96             },
97             },
98             },
99             lang => {
100             type => SCALAR,
101             optional => true,
102             regex => qr!^(?:en)$!i,
103             },
104             format => {
105             type => SCALAR,
106             optional => true,
107             regex => qr!^(?:
108             (?: (?: [dmy]{1,4}[-./] ){2}[dmy]{1,4} )
109             |
110             (?: [dm]{1,2}/[dm]{1,2} )
111             )$!ix,
112             },
113             prefer_future => {
114             # SCALARREF due to boolean.pm's implementation
115             type => BOOLEAN | SCALARREF,
116             optional => true,
117             callbacks => {
118             'mutually exclusive' => sub
119             {
120 1090 50   1090   70472 return true unless exists $_[1]->{demand_future};
121 0         0 die "demand_future provided\n";
122             },
123             },
124             },
125             time_zone => {
126             type => SCALAR | OBJECT,
127             optional => true,
128             callbacks => {
129             'valid timezone' => sub
130             {
131 1562     1562   86536 my $val = shift;
132 1562 100       5578 if (blessed($val)) {
133 1         18 return $val->isa('DateTime::TimeZone');
134             }
135             else {
136 1561         2967 eval { DateTime::TimeZone->new(name => $val) };
  1561         6087  
137 1561         127587 return !$@;
138             }
139             }
140             },
141             },
142             daytime => {
143             type => HASHREF,
144             optional => true,
145             callbacks => {
146             'valid daytime' => sub
147             {
148 39     39   2108 my $href = shift;
149 39         91 my %daytimes = map { $_ => true } qw(morning afternoon evening);
  117         368  
150 39 50       521 if (any { !$daytimes{$_} } keys %$href) {
  58 50       375  
    50          
    50          
151 0         0 die "spelling of daytime\n";
152             }
153 58         324 elsif (any { !defined $href->{$_} } keys %$href) {
154 0         0 die "undefined hour\n";
155             }
156 58         300 elsif (any { $href->{$_} !~ /^\d{1,2}$/ } keys %$href) {
157 0         0 die "not a valid number\n";
158             }
159 58 50       216 elsif (any { $href->{$_} < 0 || $href->{$_} > 23 } keys %$href) {
160 0         0 die "hour out of range\n";
161             }
162             else {
163 39         96 return true;
164             }
165             }
166             },
167             },
168             datetime => {
169             type => OBJECT,
170             optional => true,
171             callbacks => {
172             'valid object' => sub
173             {
174 26     26   911 my $obj = shift;
175 26 50       424 blessed($obj) && $obj->isa('DateTime');
176             }
177             },
178             },
179 10582         41210 });
180             }
181              
182             sub _init_vars
183             {
184 11703     11703   21402 my $self = shift;
185              
186 11703         36632 delete @$self{qw(keyword modified postprocess)};
187             }
188              
189             sub parse_datetime
190             {
191 11703     11703 1 64126 my $self = shift;
192              
193 11703         37754 $self->_parse_init(@_);
194              
195 11703         91801 $self->{input_string} = $self->{date_string};
196              
197 11703         21731 $self->{mode} = 'parse';
198              
199 11703         21234 my $date_string = $self->{date_string};
200              
201 11703         48734 $self->_rewrite(\$date_string);
202              
203 11703         76257 my ($formatted) = $date_string =~ $self->{data}->__regexes('format');
204 11703         48469 my %count = $self->_count_separators($formatted);
205              
206 11703         31806 $self->{tokens} = [];
207 11703         25224 $self->{traces} = [];
208              
209 11703 100       35994 if ($self->_check_formatted('ymd', \%count)) {
    100          
    100          
    100          
    100          
210 271         1074 my $dt = $self->_parse_formatted_ymd($date_string, \%count);
211 271 100       1049 return $dt if blessed($dt);
212             }
213             elsif ($self->_check_formatted('md', \%count)) {
214 193         750 my $dt = $self->_parse_formatted_md($date_string);
215 193 100       714 return $dt if blessed($dt);
216              
217 192 100 100     534 if ($self->{Prefer_future} || $self->{Demand_future}) {
218 36         509 $self->_advance_future('md');
219             }
220             }
221             elsif ($date_string =~ /^(\d{4}(?:-\d{2}){0,2})T(\d{2}(?::\d{2}){0,2})$/) {
222 9         39 my ($date, $time) = ($1, $2);
223              
224 9         12 my %args;
225              
226 9         50 @args{qw(year month day)} = split /-/, $date;
227 9   100     57 $args{$_} ||= 01 foreach qw(month day);
228              
229 9         32 @args{qw(hour minute second)} = split /:/, $time;
230 9   100     42 $args{$_} ||= 00 foreach qw(minute second);
231              
232 9         65 my $valid_date = $self->_check_date(map $args{$_}, qw(year month day));
233 9         53 my $valid_time = $self->_check_time(map $args{$_}, qw(hour minute second));
234              
235 9 50 33     40 if (not $valid_date && $valid_time) {
236 0 0       0 my $type = !$valid_date ? 'date' : 'time';
237 0         0 $self->_set_failure;
238 0         0 $self->_set_error("(invalid $type)");
239 0         0 return $self->_get_datetime_object;
240             }
241              
242 9         49 $self->_set(%args);
243              
244 9         39 $self->{datetime}->truncate(to => 'second');
245 9         2456 $self->_set_truncated;
246 9         43 $self->_set_valid_exp;
247             }
248             elsif ($date_string =~ /^([+-]) (\d+?) ([a-zA-Z]+)$/x) {
249 14         81 my ($prefix, $value, $unit) = ($1, $2, lc $3);
250              
251 14         55 my %methods = (
252             '+' => '_add',
253             '-' => '_subtract',
254             );
255 14         32 my $method = $methods{$prefix};
256              
257 14 100   64   54 if (none { $unit =~ /^${_}s?$/ } @{$self->{data}->__units('ordered')}) {
  64         679  
  14         93  
258 2         8 $self->_set_failure;
259 2         12 $self->_set_error("(invalid unit)");
260 2         6 return $self->_get_datetime_object;
261             }
262 12         107 $self->$method($unit => $value);
263              
264 12         39 $self->_set_valid_exp;
265             }
266             elsif ($date_string =~ /^\d{14}$/) {
267 6         11 my %args;
268 6         46 @args{qw(year month day hour minute second)} = $date_string =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/;
269              
270 6         43 my $valid_date = $self->_check_date(map $args{$_}, qw(year month day));
271 6         38 my $valid_time = $self->_check_time(map $args{$_}, qw(hour minute second));
272              
273 6 50 33     37 if (not $valid_date && $valid_time) {
274 0 0       0 my $type = !$valid_date ? 'date' : 'time';
275 0         0 $self->_set_failure;
276 0         0 $self->_set_error("(invalid $type)");
277 0         0 return $self->_get_datetime_object;
278             }
279              
280 6         31 $self->_set(%args);
281              
282 6         53 $self->{datetime}->truncate(to => 'second');
283 6         1548 $self->_set_truncated;
284 6         28 $self->_set_valid_exp;
285             }
286             else {
287 11210         22140 @{$self->{tokens}} = split /\s+/, $date_string;
  11210         58935  
288 11210         65367 $self->{data}->__init('tokens')->($self);
289 11210         19622 $self->{count}{tokens} = @{$self->{tokens}};
  11210         34498  
290              
291 11210         31956 $self->_process;
292             }
293              
294 11699         232834 my $trace = $self->_trace_string;
295 11699 100       38267 if (defined $trace) {
296 11339         19344 @{$self->{traces}} = $trace;
  11339         33434  
297             }
298              
299 11699         35155 return $self->_get_datetime_object;
300             }
301              
302             sub _params_init
303             {
304 13170     13170   21946 my $self = shift;
305 13170         21765 my $params = pop;
306              
307 13170 50       34524 if (@_ > 1) {
308 0         0 validate(@_, { string => { type => SCALAR }});
309 0         0 my %opts = @_;
310 0         0 foreach my $opt (keys %opts) {
311 0         0 ${$params->{$opt}} = $opts{$opt};
  0         0  
312             }
313             }
314             else {
315 13170         176022 validate_pos(@_, { type => SCALAR });
316 13170         45777 (${$params->{string}}) = @_;
  13170         35415  
317             }
318              
319 13170         50199 trim($params->{string});
320             }
321              
322             sub _parse_init
323             {
324 11703     11703   21221 my $self = shift;
325              
326 11703         51834 $self->_params_init(@_, { string => \$self->{date_string} });
327              
328             my $set_datetime = sub
329             {
330 2907     2907   7001 my ($method, $args) = @_;
331              
332 2907 100 66     9125 if (exists $self->{Datetime} && $method eq 'now') {
333 24         267 $self->{datetime} = dclone($self->{Datetime});
334             }
335             else {
336             $self->{datetime} = DateTime::HiRes->$method(
337             time_zone => $self->{Time_zone},
338 2883         15222 %$args,
339             );
340             }
341 11703         61073 };
342              
343 11703 100       42799 if ($self->{running_tests}) {
344 8796         80978 $self->{datetime} = $self->{datetime_test}->clone;
345             }
346             else {
347 2907         6741 $set_datetime->('now', {});
348             }
349              
350 11703         1663856 $self->_init_vars;
351              
352 11703         35019 $self->_unset_failure;
353 11703         56525 $self->_unset_error;
354 11703         32570 $self->_unset_valid_exp;
355 11703         59401 $self->_unset_trace;
356 11703         27347 $self->_unset_truncated;
357             }
358              
359             sub parse_datetime_duration
360             {
361 1258     1258 1 204266 my $self = shift;
362              
363 1258         2445 my $duration_string;
364 1258         6435 $self->_params_init(@_, { string => \$duration_string });
365 1258         9320 my $timespan_sep = $self->{data}->__timespan('literal');
366              
367             my @date_strings = $duration_string =~ /\s+ $timespan_sep \s+/ix
368 1039         3230 ? do { $self->{duration} = true;
369 1039         9671 split /\s+ $timespan_sep \s+/ix, $duration_string }
370 1258 100       8797 : do { $self->{duration} = false;
  219         836  
371 219         1106 ($duration_string) };
372              
373 1258         2775 my $max = 2;
374              
375 1258         3042 my $shrinked = false;
376 1258 100       5620 if (@date_strings > $max) {
377 1         3 my $offset = $max;
378 1         2 splice (@date_strings, $offset);
379 1         3 $shrinked = true;
380             }
381              
382 1258         6037 $self->_rewrite_duration(\@date_strings);
383              
384 1258         7615 $self->_pre_duration(\@date_strings);
385 1258         17864 @$self{qw(state truncated_duration)} = ({}, []);
386              
387 1258         2976 my (@queue, @traces, @truncated);
388 1258         3022 foreach my $date_string (@date_strings) {
389 2297         6680 push @queue, $self->parse_datetime($date_string);
390 2297         6466 $self->_save_state(
391             valid_expression => $self->_get_valid_exp,
392             failure => $self->_get_failure,
393             error => $self->_get_error,
394             );
395 2297 100       19646 if (@{$self->{traces}}) {
  2297         6280  
396 2292         5367 push @traces, $self->{traces}[0];
397             }
398 2297 100       5955 if ($self->{running_tests}) {
399 1932         14325 push @truncated, $self->_get_truncated;
400             }
401             }
402              
403 1258         6452 $self->_post_duration(\@queue, \@traces, \@truncated);
404 1258         7674 $self->_restore_state;
405              
406 1258         5347 delete @$self{qw(duration insert state)};
407              
408 1258         2443 @{$self->{traces}} = @traces;
  1258         3741  
409 1258         2288 @{$self->{truncated_duration}} = @truncated;
  1258         2848  
410 1258         2711 $self->{input_string} = $duration_string;
411              
412 1258 100       3386 if ($shrinked) {
413 1         11 $self->_set_failure;
414 1         13 $self->_set_error("(limit of $max duration substrings exceeded)");
415             }
416              
417 1258         15241 return @queue;
418             }
419              
420             sub extract_datetime
421             {
422 209     209 1 4964 my $self = shift;
423              
424 209         399 my $extract_string;
425 209         1061 $self->_params_init(@_, { string => \$extract_string });
426              
427 209         1010 $self->_unset_failure;
428 209         1126 $self->_unset_error;
429 209         582 $self->_unset_valid_exp;
430              
431 209         966 $self->{input_string} = $extract_string;
432              
433 209         569 $self->{mode} = 'extract';
434              
435 209         1093 my @expressions = $self->_extract_expressions($extract_string);
436              
437 209 100       2428 $self->_set_valid_exp if @expressions;
438              
439 209 100       2122 return wantarray ? @expressions : $expressions[0];
440             }
441              
442             sub success
443             {
444 10602     10602 1 69653 my $self = shift;
445              
446 10602 100 100     25734 return ($self->_get_valid_exp && !$self->_get_failure) ? true : false;
447             }
448              
449             sub error
450             {
451 6     6 1 64 my $self = shift;
452              
453 6 100       16 return '' if $self->success;
454              
455             my $error = sub
456             {
457 5 100 66 5   35 return undef unless defined $self->{mode} && length $self->{mode};
458 4         36 my %errors = (
459             extract => "'$self->{input_string}' cannot be extracted from",
460             parse => "'$self->{input_string}' does not parse",
461             );
462 4         15 return $errors{$self->{mode}};
463 5         142 }->();
464              
465 5 100       32 if (defined $error) {
466 4   100     21 $error .= ' ' . ($self->_get_error || '(perhaps you have some garbage?)');
467             }
468             else {
469 1         3 $error = 'neither extracting nor parsing method invoked';
470             }
471              
472 5         44 return $error;
473             }
474              
475             sub trace
476             {
477 7     7 1 56 my $self = shift;
478              
479 7 100       11 return @{$self->{traces} || []};
  7         45  
480             }
481              
482             sub _process
483             {
484 11531     11531   21100 my $self = shift;
485              
486 11531         18595 my %opts;
487              
488 11531 100       30861 if (!exists $self->{lookup}) {
489 10414         16112 foreach my $keyword (keys %{$self->{data}->__grammar('')}) {
  10414         48481  
490 697738         5412121 my $count = scalar @{$self->{data}->__grammar($keyword)->[0]};
  697738         2569562  
491 697738         1128650 push @{$self->{lookup}{$count}}, [ $keyword, false ];
  697738         1770375  
492 697738 100       2981901 if ($self->_expand_for($keyword)) {
493 197866         1761486 push @{$self->{lookup}{$count + 1}}, [ $keyword, true ];
  197866         509019  
494             }
495             }
496             }
497              
498 11531 50       129625 PARSE: foreach my $lookup (@{$self->{lookup}{$self->{count}{tokens}} || []}) {
  11531         52460  
499 111614         1537686 my ($keyword, $expandable) = @$lookup;
500              
501 111614         165728 my @grammar = @{$self->{data}->__grammar($keyword)};
  111614         545725  
502 111614         213165 my $types_entry = shift @grammar;
503              
504 111614 100       269197 @grammar = $self->_expand($keyword, $types_entry, \@grammar) if $expandable;
505              
506 111614         744537 foreach my $entry (@grammar) {
507 669041 100       4486307 my ($types, $expression) = $expandable ? @$entry : ($types_entry, $entry);
508 669041         4705131 my $valid_expression = true;
509 669041         2021954 my $definition = $expression->[0];
510 669041         1922781 my @positions = sort {$a <=> $b} keys %$definition;
  1844039         3539442  
511 669041         1154313 my (%first_stack, %rest_stack);
512 669041         1066095 foreach my $pos (@positions) {
513 775069 100       1734612 if ($types->[$pos] eq 'SCALAR') {
    50          
514 65024 50       139585 if (defined $definition->{$pos}) {
515 65024 100       88076 if (${$self->_token($pos)} =~ /^$definition->{$pos}$/i) {
  65024         120527  
516 6354         16660 next;
517             }
518             else {
519 58670         148519 $valid_expression = false;
520 58670         196796 last;
521             }
522             }
523             }
524             elsif ($types->[$pos] eq 'REGEXP') {
525 710045 100       973930 if (my @captured = ${$self->_token($pos)} =~ $definition->{$pos}) {
  710045         1292169  
526 111211         269581 $first_stack{$pos} = shift @captured;
527 111211         205246 $rest_stack{$pos} = [ @captured ];
528 111211         254490 next;
529             }
530             else {
531 598834         1321743 $valid_expression = false;
532 598834         1844212 last;
533             }
534             }
535             else {
536 0         0 die "grammar error at keyword \"$keyword\" within $self->{grammar_class}: ",
537             "unknown type $types->[$pos]\n";
538             }
539             }
540 669041 100 100     1563220 if ($valid_expression && @{$expression->[2]}) {
  11537         115155  
541 8573         16372 my $i = 0;
542 8573         14806 foreach my $check (@{$expression->[2]}) {
  8573         20706  
543 10839         17909 my @pos = @{$expression->[1][$i++]};
  10839         28125  
544 10839         18235 my $error;
545 10839         39955 $valid_expression &= $check->(\%first_stack, \%rest_stack, \@pos, \$error);
546 10839 100       66683 unless ($valid_expression) {
547 366         1735 $self->_set_error("($error)");
548 366         832 last;
549             }
550             }
551             }
552 669041 100       4847932 if ($valid_expression) {
553 11171         51624 $self->_set_valid_exp;
554 11171 100       40320 my @truncate_to = @{$expression->[6]->{truncate_to} || []};
  11171         44274  
555 11171         20746 my $i = 0;
556 11171         17436 foreach my $positions (@{$expression->[3]}) {
  11171         25445  
557 19578         33532 my ($c, @values);
558 19578         36753 foreach my $pos (@$positions) {
559 24633 100       86184 my $index = ref $pos eq 'HASH' ? (keys %$pos)[0] : $pos;
560             $values[$c++] = ref $pos
561             ? $index eq 'VALUE'
562             ? $pos->{$index}
563             : $self->SUPER::_helper($pos->{$index}, $first_stack{$index})
564             : exists $first_stack{$index}
565             ? $first_stack{$index}
566 24633 100       123651 : ${$self->_token($index)};
  0 50       0  
    100          
567             }
568 19578         52143 my $worker = "SUPER::$expression->[5]->[$i]";
569 19578         96954 $self->$worker(@values, $expression->[4]->[$i++]);
570 19578         85911 $self->_truncate(shift @truncate_to);
571             }
572 11171         22245 %opts = %{$expression->[6]};
  11171         36146  
573 11171         28016 $self->{keyword} = $keyword;
574 11171         93098 last PARSE;
575             }
576             }
577             }
578              
579 11531         38376 $self->_post_process(%opts);
580             }
581              
582             sub _truncate
583             {
584 19578     19578   34802 my $self = shift;
585 19578         40237 my ($truncate_to) = @_;
586              
587 19578 100       62846 return unless defined $truncate_to;
588              
589 11327 100       22936 my @truncate_to = map { $_ =~ /_/ ? split /_/, $_ : $_ } $truncate_to;
  11327         72313  
590 11327         23049 my $i = 0;
591 11327         18224 my @units = @{$self->{data}->__units('ordered')};
  11327         74992  
592 11327         28653 my %indexes = map { $_ => $i++ } @units;
  90616         178335  
593 11327         31604 foreach my $unit (@truncate_to) {
594 20001         39352 my $index = $indexes{$unit} - 1;
595 20001 100 66     89242 if (defined $units[$index] && !exists $self->{modified}{$units[$index]}) {
596 11273         47070 $self->{datetime}->truncate(to => $unit);
597 11273         3342510 $self->_set_truncated;
598 11273         86682 last;
599             }
600             }
601             }
602              
603             sub _post_process
604             {
605 11531     11531   20450 my $self = shift;
606 11531         27148 my %opts = @_;
607              
608 11531         24763 delete $opts{truncate_to};
609              
610 11531 50 100     44079 if (($self->{Prefer_future} || $self->{Demand_future})
      66        
      66        
611             && (exists $opts{advance_future} && $opts{advance_future})
612             ) {
613 2070         41399 $self->_advance_future;
614             }
615             }
616              
617             sub _advance_future
618             {
619 2106     2106   3759 my $self = shift;
620 2106         4279 my %advance = map { $_ => true } @_;
  36         79  
621              
622 2106         3658 my %modified = map { $_ => true } keys %{$self->{modified}};
  6156         18968  
  2106         6998  
623             my $token_contains = sub
624             {
625 2520     2520   41017 my ($identifier) = @_;
626             return any {
627 27066         67864 my $data = $_;
628             any {
629 43158         72848 my $token = $_;
630 43158         238447 $token =~ /^$data$/i;
631 27066         67532 } @{$self->{tokens}}
  27066         62343  
632 2520         9810 } @{$self->{data}->{$identifier}};
  2520         10964  
633 2106         16897 };
634              
635             my $now = exists $self->{Datetime}
636             ? dclone($self->{Datetime})
637 2106 100       10079 : DateTime::HiRes->now(time_zone => $self->{Time_zone});
638              
639 2106     1530   1045281 my $day_of_week = sub { $_[0]->_Day_of_Week(map $_[0]->{datetime}->$_, qw(year month day)) };
  1530         52420  
640              
641 2106         6791 my $skip_weekdays = false;
642              
643 2106 50 33 4388   14244 if ((all { /^(?:(?:nano)?second|minute|hour)$/ } keys %modified)
  4388 100 66     26106  
    100 100        
    100 66        
    100 66        
    100 66        
      66        
      100        
      33        
      66        
      66        
      33        
      100        
      66        
      33        
      66        
      33        
      66        
      33        
644             && (exists $self->{modified}{hour} && $self->{modified}{hour} == 1)
645             && (($self->{Prefer_future} && $self->{datetime} < $now)
646             || ($self->{Demand_future} && $self->{datetime} <= $now))
647             ) {
648 234         25226 $self->{postprocess}{day} = 1;
649             }
650             elsif (sub {
651 1872 100   1872   26313 return false unless @{$self->{tokens}} == 2;
  1872         6697  
652 1398         10586 my ($day, $weekday) = map $self->{data}->__RE($_), qw(day weekday);
653 1398 100 100     10615 if ($self->{tokens}->[0] =~ $day
654             && $self->{tokens}->[1] =~ $weekday) {
655 36         156 $skip_weekdays = true;
656 36         140 return true;
657             }
658 1362         4155 return false;
659             }->()
660 108     108   972 && (all { /^(?:day|month|year)$/ } keys %modified)
661             && (($self->{Prefer_future} && $self->{datetime}->day < $now->day)
662             || ($self->{Demand_future} && $self->{datetime}->day <= $now->day))
663             ) {
664 18         766 $self->{postprocess}{week} = 4;
665             }
666             elsif (($token_contains->('weekdays_all') && !$skip_weekdays)
667             && (exists $self->{modified}{day} && $self->{modified}{day} == 1)
668             && (($self->{Prefer_future} && $day_of_week->($self) < $now->wday)
669             || ($self->{Demand_future} && $day_of_week->($self) <= $now->wday))
670             ) {
671 1188         18304 $self->{postprocess}{day} = 7;
672             }
673             elsif (($token_contains->('months_all') || $advance{md})
674 156     156   1926 && (all { /^(?:day|month)$/ } keys %modified)
675             && (exists $self->{modified}{month} && $self->{modified}{month} == 1)
676             && (exists $self->{modified}{day}
677             ? $self->{modified}{day} == 1
678             ? true : false
679             : true)
680             && (($self->{Prefer_future} && $self->{datetime}->day_of_year < $now->day_of_year)
681             || ($self->{Demand_future} && $self->{datetime}->day_of_year <= $now->day_of_year))
682             ) {
683 72         3291 $self->{postprocess}{year} = 1;
684             }
685             }
686              
687             sub _token
688             {
689 775069     775069   1109855 my $self = shift;
690 775069         1240561 my ($pos) = @_;
691              
692 775069         1095727 my $str = '';
693 775069         1547344 my $token = $self->{tokens}->[0 + $pos];
694              
695 775069 50       4168393 return defined $token
696             ? \$token
697             : \$str;
698             }
699              
700 19578     19578   30664 sub _register_trace { push @{$_[0]->{trace}}, (caller(1))[3] }
  19578         169726  
701 11703     11703   20205 sub _unset_trace { @{$_[0]->{trace}} = () }
  11703         33007  
702              
703 2652     2652   17899 sub _get_error { $_[0]->{error} }
704 372     372   952 sub _set_error { $_[0]->{error} = $_[1] }
705 11914     11914   24734 sub _unset_error { $_[0]->{error} = undef }
706              
707 12538     12538   110128 sub _get_failure { $_[0]->{failure} }
708 6     6   35 sub _set_failure { $_[0]->{failure} = true }
709 11914     11914   30601 sub _unset_failure { $_[0]->{failure} = false }
710              
711 12899     12899   47590 sub _get_valid_exp { $_[0]->{valid_expression} }
712 11868     11868   26923 sub _set_valid_exp { $_[0]->{valid_expression} = true }
713 12236     12236   25696 sub _unset_valid_exp { $_[0]->{valid_expression} = false }
714              
715 10779     10779   188786 sub _get_truncated { $_[0]->{truncated} }
716 11750     11750   35925 sub _set_truncated { $_[0]->{truncated} = true }
717 12024     12024   25964 sub _unset_truncated { $_[0]->{truncated} = false }
718              
719             sub _get_datetime_object
720             {
721 11703     11703   20062 my $self = shift;
722              
723             my $dt = DateTime->new(
724             time_zone => $self->{datetime}->time_zone,
725             year => $self->{datetime}->year,
726             month => $self->{datetime}->month,
727             day => $self->{datetime}->day_of_month,
728             hour => $self->{datetime}->hour,
729             minute => $self->{datetime}->minute,
730             second => $self->{datetime}->second,
731             nanosecond => $self->{datetime}->nanosecond,
732 11703         42199 );
733              
734 11703         3733263 foreach my $unit (keys %{$self->{postprocess}}) {
  11703         53783  
735 1512         7044 $dt->add("${unit}s" => $self->{postprocess}{$unit});
736             }
737              
738 11703         1563652 return $dt;
739             }
740              
741             # solely for testing purpose
742             sub _set_datetime
743             {
744 7806     7806   34654 my $self = shift;
745 7806         18858 my ($time, $tz) = @_;
746              
747 7806   100     59060 $self->{datetime_test} = DateTime->new(
748             time_zone => $tz || 'floating',
749             %$time,
750             );
751 7806         2985026 $self->{running_tests} = true;
752             }
753              
754             1;
755             __END__
756              
757             =encoding ISO8859-1
758              
759             =head1 NAME
760              
761             DateTime::Format::Natural - Parse informal natural language date/time strings
762              
763             =head1 SYNOPSIS
764              
765             use DateTime::Format::Natural;
766              
767             $parser = DateTime::Format::Natural->new;
768              
769             $dt = $parser->parse_datetime($date_string);
770             @dt = $parser->parse_datetime_duration($date_string);
771              
772             $date_string = $parser->extract_datetime($extract_string);
773             @date_strings = $parser->extract_datetime($extract_string);
774              
775             if ($parser->success) {
776             # operate on $dt/@dt, for example:
777             print $dt->strftime('%d.%m.%Y %H:%M:%S'), "\n";
778             } else {
779             warn $parser->error;
780             }
781              
782             @traces = $parser->trace;
783              
784             # examples
785              
786             12:14 PM
787             next tuesday at 2am
788             tomorrow morning
789             4pm yesterday
790             10 weeks ago
791              
792             1st tuesday last november
793             2nd friday in august
794             final thursday in april
795              
796             for 3 hours
797             monday to friday
798             1 April 10 am to 1 May 8am
799              
800             jan 24, 2011 12:00
801              
802             =head1 DESCRIPTION
803              
804             C<DateTime::Format::Natural> parses informal natural language date/time strings.
805             In addition, parsable date/time substrings may be extracted from ordinary strings.
806              
807             =head1 CONSTRUCTOR
808              
809             =head2 new
810              
811             Creates a new C<DateTime::Format::Natural> object. Arguments to C<new()> are options and
812             not necessarily required.
813              
814             $parser = DateTime::Format::Natural->new(
815             datetime => DateTime->new(...),
816             lang => 'en',
817             format => 'mm/dd/yy',
818             prefer_future => [0|1],
819             demand_future => [0|1],
820             time_zone => 'floating',
821             daytime => { morning => 06,
822             afternoon => 13,
823             evening => 20,
824             },
825             );
826              
827             =over 4
828              
829             =item * C<datetime>
830              
831             Overrides the present now with a L<DateTime> object provided.
832              
833             =item * C<lang>
834              
835             Contains the language selected, currently limited to C<en> (english).
836             Defaults to 'C<en>'.
837              
838             =item * C<format>
839              
840             Specifies the format of numeric dates.
841              
842             The format is used to influence how numeric dates are parsed. Given two
843             numbers separated by a slash, the month/day order expected comes from
844             this option. If there is a third number, this option describes where
845             to expect the year. When this format can't be used to interpret the
846             date, some unambiguous dates may be parsed, but there is no form
847             guarantee.
848              
849             Current supported "month/day" formats: C<dd/mm>, C<mm/dd>.
850              
851             Current supported "year/month/day" formats (with slashes): C<dd/mm/yy>,
852             C<dd/mm/yyyy>, C<mm/dd/yyyy>, C<yyyy/mm/dd>.
853              
854             Note that all of the above formats with three units do also parse
855             with dots or dashes as format separators.
856              
857             Furthermore, formats can be abbreviated as long as they remain
858             unambiguous.
859              
860             Defaults to 'C<d/m/y>'.
861              
862             =item * C<prefer_future>
863              
864             Prefers future time and dates. Accepts a boolean, defaults to false.
865              
866             =item * C<demand_future>
867              
868             Demands future time and dates. Similar to C<prefer_future>, but stronger.
869             Accepts a boolean, defaults to false.
870              
871             =item * C<time_zone>
872              
873             The time zone to use when parsing and for output. Accepts any time zone
874             recognized by L<DateTime>. Defaults to 'floating'.
875              
876             =item * C<daytime>
877              
878             A hash reference consisting of customized daytime hours,
879             which may be selectively changed.
880              
881             =back
882              
883             =head1 METHODS
884              
885             =head2 parse_datetime
886              
887             Returns a L<DateTime> object constructed from a natural language date/time string.
888              
889             $dt = $parser->parse_datetime($date_string);
890             $dt = $parser->parse_datetime(string => $date_string);
891              
892             =over 4
893              
894             =item * C<string>
895              
896             The date string.
897              
898             =back
899              
900             =head2 parse_datetime_duration
901              
902             Returns one or two L<DateTime> objects constructed from a natural language
903             date/time string which may contain timespans/durations. I<Same> interface
904             and options as C<parse_datetime()>, but should be explicitly called in
905             list context.
906              
907             @dt = $parser->parse_datetime_duration($date_string);
908             @dt = $parser->parse_datetime_duration(string => $date_string);
909              
910             =head2 extract_datetime
911              
912             Returns parsable date/time substrings (also known as expressions) extracted
913             from the string provided; in scalar context only the first parsable substring
914             is returned, whereas in list context all parsable substrings are returned.
915             Each extracted substring can then be passed to the C<parse_datetime()>/
916             C<parse_datetime_duration()> methods.
917              
918             $date_string = $parser->extract_datetime($extract_string);
919             @date_strings = $parser->extract_datetime($extract_string);
920             # or
921             $date_string = $parser->extract_datetime(string => $extract_string);
922             @date_strings = $parser->extract_datetime(string => $extract_string);
923              
924             =head2 success
925              
926             Returns a boolean indicating success or failure for parsing the date/time
927             string given.
928              
929             =head2 error
930              
931             Returns the error message if the parsing did not succeed.
932              
933             =head2 trace
934              
935             Returns one or two strings with the grammar keyword for the valid
936             expression parsed, traces of methods which were called within the Calc
937             class and a summary how often certain units have been modified. More than
938             one string is commonly returned for durations. Useful as a debugging aid.
939              
940             =head1 GRAMMAR
941              
942             The grammar handling has been rewritten to be easily extendable and hence
943             everybody is encouraged to propose sensible new additions and/or changes.
944              
945             See the class L<DateTime::Format::Natural::Lang::EN> if you're intending
946             to hack a bit on the grammar guts.
947              
948             =head1 EXAMPLES
949              
950             See the class L<DateTime::Format::Natural::Lang::EN> for an overview of
951             currently valid input.
952              
953             =head1 BUGS & CAVEATS
954              
955             C<parse_datetime()>/C<parse_datetime_duration()> always return one or two
956             DateTime objects regardless whether the parse was successful or not. In
957             case no valid expression was found or a failure occurred, an unaltered
958             DateTime object with its initial values (most often the "current" now) is
959             likely to be returned. It is therefore recommended to use C<success()> to
960             assert that the parse did succeed (at least, for common uses), otherwise
961             the absence of a parse failure cannot be guaranteed.
962              
963             C<parse_datetime()> is not capable of handling durations.
964              
965             =head1 CREDITS
966              
967             Thanks to Tatsuhiko Miyagawa for the initial inspiration. See Miyagawa's journal
968             entry L<http://use.perl.org/~miyagawa/journal/31378> for more information.
969              
970             Furthermore, thanks to (in order of appearance) who have contributed
971             valuable suggestions and patches:
972              
973             Clayton L. Scott
974             Dave Rolsky
975             CPAN Author 'SEKIMURA'
976             mike (pulsation)
977             Mark Stosberg
978             Tuomas Jormola
979             Cory Watson
980             Urs Stotz
981             Shawn M. Moore
982             Andreas J. König
983             Chia-liang Kao
984             Jonny Schulz
985             Jesse Vincent
986             Jason May
987             Pat Kale
988             Ankur Gupta
989             Alex Bowley
990             Elliot Shank
991             Anirvan Chatterjee
992             Michael Reddick
993             Christian Brink
994             Giovanni Pensa
995             Andrew Sterling Hanenkamp
996             Eric Wilhelm
997             Kevin Field
998             Wes Morgan
999             Vladimir Marek
1000             Rod Taylor
1001             Tim Esselens
1002             Colm Dougan
1003             Chifung Fan
1004             Xiao Yafeng
1005             Roman Filippov
1006             David Steinbrunner
1007             Debian Perl Group
1008             Tim Bunce
1009             Ricardo Signes
1010             Felix Ostmann
1011             Jörn Clausen
1012             Jim Avera
1013             Olaf Alders
1014             Karen Etheridge
1015              
1016             =head1 SEE ALSO
1017              
1018             L<dateparse>, L<DateTime>, L<Date::Calc>, L<http://datetime.perl.org>
1019              
1020             =head1 AUTHOR
1021              
1022             Steven Schubiger <schubiger@cpan.org>
1023              
1024             =head1 LICENSE
1025              
1026             This program is free software; you may redistribute it and/or
1027             modify it under the same terms as Perl itself.
1028              
1029             See L<http://dev.perl.org/licenses/>
1030              
1031             =cut