File Coverage

blib/lib/DateTime/Format/Natural.pm
Criterion Covered Total %
statement 398 418 95.2
branch 131 154 85.0
condition 73 106 68.8
subroutine 60 60 100.0
pod 7 7 100.0
total 669 745 89.8


line stmt bran cond sub pod time code
1             package DateTime::Format::Natural;
2              
3 26     26   2823182 use strict;
  26         61  
  26         1114  
4 26     26   143 use warnings;
  26         89  
  26         2046  
5 26         14955 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   181 );
  26         53  
14 26     26   198 use boolean qw(true false);
  26         54  
  26         168  
15              
16 26     26   2082 use Carp qw(croak);
  26         55  
  26         1376  
17 26     26   152 use DateTime ();
  26         50  
  26         441  
18 26     26   13179 use DateTime::HiRes ();
  26         49659  
  26         678  
19 26     26   200 use DateTime::TimeZone ();
  26         78  
  26         1000  
20 26     26   250 use List::Util 1.33 qw(all any none);
  26         642  
  26         3422  
21 26     26   18453 use Params::Validate ':all';
  26         91636  
  26         6716  
22 26     26   224 use Scalar::Util qw(blessed);
  26         64  
  26         1572  
23 26     26   204 use Storable qw(dclone);
  26         118  
  26         1480  
24              
25 26     26   205 use DateTime::Format::Natural::Utils qw(trim);
  26         76  
  26         199603  
26              
27             our $VERSION = '1.25';
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 10813     10813 1 3487135 my $class = shift;
42              
43 10813   33     88458 my $self = bless {}, ref($class) || $class;
44              
45 10813         58301 $self->_init_check(@_);
46 10812         1378899 $self->_init(@_);
47              
48 10812         40416 return $self;
49             }
50              
51             sub _init
52             {
53 10812     10812   28034 my $self = shift;
54 10812         39870 my %opts = @_;
55              
56 10812         45808 my %presets = (
57             lang => 'en',
58             format => 'd/m/y',
59             demand_future => false,
60             prefer_future => false,
61             time_zone => 'floating',
62             );
63 10812         130115 foreach my $opt (keys %presets) {
64 54060         162917 $self->{ucfirst $opt} = $presets{$opt};
65             }
66 10812         38751 foreach my $opt (keys %opts) {
67 7004 50       23518 if (defined $opts{$opt}) {
68 7004         20840 $self->{ucfirst $opt} = $opts{$opt};
69             }
70             }
71 10812   100     86141 $self->{Daytime} = $opts{daytime} || {};
72              
73 10812         53073 my $mod = join '::', (__PACKAGE__, 'Lang', uc $self->{Lang});
74 10812 50       1098110 eval "require $mod" or die $@;
75              
76 10812         94021 $self->{data} = $mod->__new();
77 10812         43248 $self->{grammar_class} = $mod;
78              
79 10812         69073 $self->{mode} = '';
80             }
81              
82             sub _init_check
83             {
84 10813     10813   25509 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   97423 return true unless exists $_[1]->{prefer_future};
95 1         13 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   102473 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   133342 my $val = shift;
132 1562 100       5756 if (blessed($val)) {
133 1         20 return $val->isa('DateTime::TimeZone');
134             }
135             else {
136 1561         2932 eval { DateTime::TimeZone->new(name => $val) };
  1561         11359  
137 1561         182915 return !$@;
138             }
139             }
140             },
141             },
142             daytime => {
143             type => HASHREF,
144             optional => true,
145             callbacks => {
146             'valid daytime' => sub
147             {
148 39     39   3271 my $href = shift;
149 39         112 my %daytimes = map { $_ => true } qw(morning afternoon evening);
  117         432  
150 39 50       812 if (any { !$daytimes{$_} } keys %$href) {
  58 50       562  
    50          
    50          
151 0         0 die "spelling of daytime\n";
152             }
153 58         421 elsif (any { !defined $href->{$_} } keys %$href) {
154 0         0 die "undefined hour\n";
155             }
156 58         379 elsif (any { $href->{$_} !~ /^\d{1,2}$/ } keys %$href) {
157 0         0 die "not a valid number\n";
158             }
159 58 50       285 elsif (any { $href->{$_} < 0 || $href->{$_} > 23 } keys %$href) {
160 0         0 die "hour out of range\n";
161             }
162             else {
163 39         214 return true;
164             }
165             }
166             },
167             },
168             datetime => {
169             type => OBJECT,
170             optional => true,
171             callbacks => {
172             'valid object' => sub
173             {
174 26     26   1544 my $obj = shift;
175 26 50       453 blessed($obj) && $obj->isa('DateTime');
176             }
177             },
178             },
179 10813         60594 });
180             }
181              
182             sub _init_vars
183             {
184 11935     11935   26406 my $self = shift;
185              
186 11935         54146 delete @$self{qw(keyword modified postprocess)};
187             }
188              
189             sub parse_datetime
190             {
191 11935     11935 1 97866 my $self = shift;
192              
193 11935         58417 $self->_parse_init(@_);
194              
195 11935         138453 $self->{input_string} = $self->{date_string};
196              
197 11935         33325 $self->{mode} = 'parse';
198              
199 11935         30627 my $date_string = $self->{date_string};
200              
201 11935         81272 $self->_rewrite(\$date_string);
202              
203 11935         102565 my ($formatted) = $date_string =~ $self->{data}->__regexes('format');
204 11935         76908 my %count = $self->_count_separators($formatted);
205              
206 11935         40757 $self->{tokens} = [];
207 11935         34187 $self->{traces} = [];
208              
209 11935 100       52698 if ($self->_check_formatted('ymd', \%count)) {
    100          
    100          
    100          
    100          
210 271         1331 my $dt = $self->_parse_formatted_ymd($date_string, \%count);
211 271 100       1032 return $dt if blessed($dt);
212             }
213             elsif ($self->_check_formatted('md', \%count)) {
214 193         999 my $dt = $self->_parse_formatted_md($date_string);
215 193 100       815 return $dt if blessed($dt);
216              
217 192 100 100     849 if ($self->{Prefer_future} || $self->{Demand_future}) {
218 36         634 $self->_advance_future('md');
219             }
220             }
221             elsif ($date_string =~ /^(\d{4}(?:-\d{2}){0,2})T(\d{2}(?::\d{2}){0,2})(?:[.,](\d+))?(Z|[+-]\d{2}(?::?\d{2})?)?$/) {
222 29         191 my ($date, $time, $fractional, $tz) = ($1, $2, $3, $4);
223 29         48 my %args;
224              
225 29 100       76 if (defined $tz) {
226 15 100       75 if ($tz eq 'Z') {
    100          
227 3         17 $self->{datetime}->set_time_zone('UTC');
228             } elsif ($tz =~ /^([+-])(\d{2})$/) {
229 5         21 $tz = "$1$2:00";
230             } else {
231 7         99 $tz =~ s/^([+-])(\d{2}):?(\d{2})$/$1$2:$3/;
232             }
233 15         1886 $self->{datetime}->set_time_zone($tz);
234             }
235              
236 29         7011 @args{qw(year month day)} = split /-/, $date;
237 29   100     220 $args{$_} ||= 01 foreach qw(month day);
238              
239 29         121 @args{qw(hour minute second)} = split /:/, $time;
240 29   100     151 $args{$_} ||= 00 foreach qw(minute second);
241              
242 29 100       93 if (defined $fractional) {
243 7         16 my $nanosecond = $fractional;
244 7 100       42 if (length($nanosecond) < 9) {
245 6         22 $nanosecond .= '0' x (9 - length($nanosecond));
246             }
247 7         25 $args{nanosecond} = int($nanosecond);
248             }
249              
250 29         245 my $valid_date = $self->_check_date(map $args{$_}, qw(year month day));
251 29         202 my $valid_time = $self->_check_time(map $args{$_}, qw(hour minute second));
252              
253 29 50 33     170 if (not $valid_date && $valid_time) {
254 0 0       0 my $type = !$valid_date ? 'date' : 'time';
255 0         0 $self->_set_failure;
256 0         0 $self->_set_error("(invalid $type)");
257 0         0 return $self->_get_datetime_object;
258             }
259              
260 29         173 $self->_set(%args);
261 29         129 $self->_set_valid_exp;
262             }
263             elsif ($date_string =~ /^([+-]) (\d+?) ([a-zA-Z]+)$/x) {
264 14         116 my ($prefix, $value, $unit) = ($1, $2, lc $3);
265              
266 14         50 my %methods = (
267             '+' => '_add',
268             '-' => '_subtract',
269             );
270 14         28 my $method = $methods{$prefix};
271              
272 14 100   64   44 if (none { $unit =~ /^${_}s?$/ } @{$self->{data}->__units('ordered')}) {
  64         747  
  14         85  
273 2         6 $self->_set_failure;
274 2         9 $self->_set_error("(invalid unit)");
275 2         5 return $self->_get_datetime_object;
276             }
277 12         88 $self->$method($unit => $value);
278              
279 12         42 $self->_set_valid_exp;
280             }
281             elsif ($date_string =~ /^\d{14}$/) {
282 6         22 my %args;
283 6         67 @args{qw(year month day hour minute second)} = $date_string =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/;
284              
285 6         79 my $valid_date = $self->_check_date(map $args{$_}, qw(year month day));
286 6         81 my $valid_time = $self->_check_time(map $args{$_}, qw(hour minute second));
287              
288 6 50 33     63 if (not $valid_date && $valid_time) {
289 0 0       0 my $type = !$valid_date ? 'date' : 'time';
290 0         0 $self->_set_failure;
291 0         0 $self->_set_error("(invalid $type)");
292 0         0 return $self->_get_datetime_object;
293             }
294              
295 6         55 $self->_set(%args);
296              
297 6         99 $self->{datetime}->truncate(to => 'second');
298 6         2494 $self->_set_truncated;
299 6         46 $self->_set_valid_exp;
300             }
301             else {
302 11422         29385 @{$self->{tokens}} = split /\s+/, $date_string;
  11422         51910  
303 11422         83184 $self->{data}->__init('tokens')->($self);
304 11422         22725 $self->{count}{tokens} = @{$self->{tokens}};
  11422         65118  
305              
306 11422         47725 $self->_process;
307             }
308              
309 11931         323319 my $trace = $self->_trace_string;
310 11931 100       51941 if (defined $trace) {
311 11569         24395 @{$self->{traces}} = $trace;
  11569         48782  
312             }
313              
314 11931         51446 return $self->_get_datetime_object;
315             }
316              
317             sub _params_init
318             {
319 13402     13402   29098 my $self = shift;
320 13402         33373 my $params = pop;
321              
322 13402 50       50929 if (@_ > 1) {
323 0         0 validate(@_, { string => { type => SCALAR }});
324 0         0 my %opts = @_;
325 0         0 foreach my $opt (keys %opts) {
326 0         0 ${$params->{$opt}} = $opts{$opt};
  0         0  
327             }
328             }
329             else {
330 13402         244439 validate_pos(@_, { type => SCALAR });
331 13402         55153 (${$params->{string}}) = @_;
  13402         44416  
332             }
333              
334 13402         69584 trim($params->{string});
335             }
336              
337             sub _parse_init
338             {
339 11935     11935   25229 my $self = shift;
340              
341 11935         94273 $self->_params_init(@_, { string => \$self->{date_string} });
342              
343             my $set_datetime = sub
344             {
345 2929     2929   8840 my ($method, $args) = @_;
346              
347 2929 100 66     12772 if (exists $self->{Datetime} && $method eq 'now') {
348 24         792 $self->{datetime} = dclone($self->{Datetime});
349             }
350             else {
351             $self->{datetime} = DateTime::HiRes->$method(
352             time_zone => $self->{Time_zone},
353 2905         20517 %$args,
354             );
355             }
356 11935         83556 };
357              
358 11935 100       59786 if ($self->{running_tests}) {
359 9006         117210 $self->{datetime} = $self->{datetime_test}->clone;
360             }
361             else {
362 2929         9826 $set_datetime->('now', {});
363             }
364              
365 11935         2142060 $self->_init_vars;
366              
367 11935         49442 $self->_unset_failure;
368 11935         82453 $self->_unset_error;
369 11935         63965 $self->_unset_valid_exp;
370 11935         100763 $self->_unset_trace;
371 11935         40022 $self->_unset_truncated;
372             }
373              
374             sub parse_datetime_duration
375             {
376 1258     1258 1 280787 my $self = shift;
377              
378 1258         2851 my $duration_string;
379 1258         8942 $self->_params_init(@_, { string => \$duration_string });
380 1258         12275 my $timespan_sep = $self->{data}->__timespan('literal');
381              
382             my @date_strings = $duration_string =~ /\s+ $timespan_sep \s+/ix
383 1039         5098 ? do { $self->{duration} = true;
384 1039         12642 split /\s+ $timespan_sep \s+/ix, $duration_string }
385 1258 100       16547 : do { $self->{duration} = false;
  219         1024  
386 219         1355 ($duration_string) };
387              
388 1258         3191 my $max = 2;
389              
390 1258         3688 my $shrinked = false;
391 1258 100       7478 if (@date_strings > $max) {
392 1         2 my $offset = $max;
393 1         2 splice (@date_strings, $offset);
394 1         3 $shrinked = true;
395             }
396              
397 1258         8714 $self->_rewrite_duration(\@date_strings);
398              
399 1258         8728 $self->_pre_duration(\@date_strings);
400 1258         23471 @$self{qw(state truncated_duration)} = ({}, []);
401              
402 1258         3594 my (@queue, @traces, @truncated);
403 1258         3607 foreach my $date_string (@date_strings) {
404 2297         9973 push @queue, $self->parse_datetime($date_string);
405 2297         9591 $self->_save_state(
406             valid_expression => $self->_get_valid_exp,
407             failure => $self->_get_failure,
408             error => $self->_get_error,
409             );
410 2297 100       25198 if (@{$self->{traces}}) {
  2297         8851  
411 2292         6413 push @traces, $self->{traces}[0];
412             }
413 2297 100       18720 if ($self->{running_tests}) {
414 1932         17695 push @truncated, $self->_get_truncated;
415             }
416             }
417              
418 1258         8053 $self->_post_duration(\@queue, \@traces, \@truncated);
419 1258         10019 $self->_restore_state;
420              
421 1258         6322 delete @$self{qw(duration insert state)};
422              
423 1258         3254 @{$self->{traces}} = @traces;
  1258         4303  
424 1258         3020 @{$self->{truncated_duration}} = @truncated;
  1258         3842  
425 1258         4569 $self->{input_string} = $duration_string;
426              
427 1258 100       4481 if ($shrinked) {
428 1         7 $self->_set_failure;
429 1         4 $self->_set_error("(limit of $max duration substrings exceeded)");
430             }
431              
432 1258         21305 return @queue;
433             }
434              
435             sub extract_datetime
436             {
437 209     209 1 5091 my $self = shift;
438              
439 209         394 my $extract_string;
440 209         1447 $self->_params_init(@_, { string => \$extract_string });
441              
442 209         1041 $self->_unset_failure;
443 209         1472 $self->_unset_error;
444 209         808 $self->_unset_valid_exp;
445              
446 209         1298 $self->{input_string} = $extract_string;
447              
448 209         574 $self->{mode} = 'extract';
449              
450 209         1467 my @expressions = $self->_extract_expressions($extract_string);
451              
452 209 100       2901 $self->_set_valid_exp if @expressions;
453              
454 209 100       2448 return wantarray ? @expressions : $expressions[0];
455             }
456              
457             sub success
458             {
459 10834     10834 1 95271 my $self = shift;
460              
461 10834 100 100     38578 return ($self->_get_valid_exp && !$self->_get_failure) ? true : false;
462             }
463              
464             sub error
465             {
466 6     6 1 46 my $self = shift;
467              
468 6 100       19 return '' if $self->success;
469              
470             my $error = sub
471             {
472 5 100 66 5   38 return undef unless defined $self->{mode} && length $self->{mode};
473 4         26 my %errors = (
474             extract => "'$self->{input_string}' cannot be extracted from",
475             parse => "'$self->{input_string}' does not parse",
476             );
477 4         19 return $errors{$self->{mode}};
478 5         104 }->();
479              
480 5 100       68 if (defined $error) {
481 4   100     18 $error .= ' ' . ($self->_get_error || '(perhaps you have some garbage?)');
482             }
483             else {
484 1         2 $error = 'neither extracting nor parsing method invoked';
485             }
486              
487 5         45 return $error;
488             }
489              
490             sub trace
491             {
492 7     7 1 58 my $self = shift;
493              
494 7 100       12 return @{$self->{traces} || []};
  7         56  
495             }
496              
497             sub _process
498             {
499 11743     11743   26047 my $self = shift;
500              
501 11743         24477 my %opts;
502              
503 11743 100       44820 if (!exists $self->{lookup}) {
504 10625         20187 foreach my $keyword (keys %{$self->{data}->__grammar('')}) {
  10625         62233  
505 743750         6731993 my $count = scalar @{$self->{data}->__grammar($keyword)->[0]};
  743750         3041932  
506 743750         1223824 push @{$self->{lookup}{$count}}, [ $keyword, false ];
  743750         2184423  
507 743750 100       3621152 if ($self->_expand_for($keyword)) {
508 201875         2114587 push @{$self->{lookup}{$count + 1}}, [ $keyword, true ];
  201875         616191  
509             }
510             }
511             }
512              
513 11743 50       191271 PARSE: foreach my $lookup (@{$self->{lookup}{$self->{count}{tokens}} || []}) {
  11743         74367  
514 129991         2433657 my ($keyword, $expandable) = @$lookup;
515              
516 129991         213315 my @grammar = @{$self->{data}->__grammar($keyword)};
  129991         851116  
517 129991         261880 my $types_entry = shift @grammar;
518              
519 129991 100       387653 @grammar = $self->_expand($keyword, $types_entry, \@grammar) if $expandable;
520              
521 129991         1001789 foreach my $entry (@grammar) {
522 734884 100       6140920 my ($types, $expression) = $expandable ? @$entry : ($types_entry, $entry);
523 734884         6296497 my $valid_expression = true;
524 734884         2562754 my $definition = $expression->[0];
525 734884         2653414 my @positions = sort {$a <=> $b} keys %$definition;
  2074361         4609199  
526 734884         1393395 my (%first_stack, %rest_stack);
527 734884         1343847 foreach my $pos (@positions) {
528 857060 100       2341416 if ($types->[$pos] eq 'SCALAR') {
    50          
529 94187 50       251972 if (defined $definition->{$pos}) {
530 94187 100       146970 if (${$self->_token($pos)} =~ /^$definition->{$pos}$/i) {
  94187         223176  
531 6417         17917 next;
532             }
533             else {
534 87770         249746 $valid_expression = false;
535 87770         321253 last;
536             }
537             }
538             }
539             elsif ($types->[$pos] eq 'REGEXP') {
540 762873 100       1180824 if (my @captured = ${$self->_token($pos)} =~ $definition->{$pos}) {
  762873         1779701  
541 127506         374990 $first_stack{$pos} = shift @captured;
542 127506         297715 $rest_stack{$pos} = [ @captured ];
543 127506         342209 next;
544             }
545             else {
546 635367         1602369 $valid_expression = false;
547 635367         2247372 last;
548             }
549             }
550             else {
551 0         0 die "grammar error at keyword \"$keyword\" within $self->{grammar_class}: ",
552             "unknown type $types->[$pos]\n";
553             }
554             }
555 734884 100 100     2029904 if ($valid_expression && @{$expression->[2]}) {
  11747         134872  
556 8765         23135 my $i = 0;
557 8765         19323 foreach my $check (@{$expression->[2]}) {
  8765         29662  
558 11067         24241 my @pos = @{$expression->[1][$i++]};
  11067         37210  
559 11067         22053 my $error;
560 11067         69552 $valid_expression &= $check->(\%first_stack, \%rest_stack, \@pos, \$error);
561 11067 100       84850 unless ($valid_expression) {
562 366         2190 $self->_set_error("($error)");
563 366         1094 last;
564             }
565             }
566             }
567 734884 100       6409984 if ($valid_expression) {
568 11381         73967 $self->_set_valid_exp;
569 11381 100       55674 my @truncate_to = @{$expression->[6]->{truncate_to} || []};
  11381         59833  
570 11381         31604 my $i = 0;
571 11381         22843 foreach my $positions (@{$expression->[3]}) {
  11381         35313  
572 19851         43215 my ($c, @values);
573 19851         47443 foreach my $pos (@$positions) {
574 24987 100       114167 my $index = ref $pos eq 'HASH' ? (keys %$pos)[0] : $pos;
575             $values[$c++] = ref $pos
576             ? $index eq 'VALUE'
577             ? $pos->{$index}
578             : $self->SUPER::_helper($pos->{$index}, $first_stack{$index})
579             : exists $first_stack{$index}
580             ? $first_stack{$index}
581 24987 100       211287 : ${$self->_token($index)};
  0 50       0  
    100          
582             }
583 19851         56859 my $worker = "SUPER::$expression->[5]->[$i]";
584 19851         135346 $self->$worker(@values, $expression->[4]->[$i++]);
585 19851         132345 $self->_truncate(shift @truncate_to);
586             }
587 11381         28885 %opts = %{$expression->[6]};
  11381         52706  
588 11381         50904 $self->{keyword} = $keyword;
589 11381         166295 last PARSE;
590             }
591             }
592             }
593              
594 11743         81295 $self->_post_process(%opts);
595             }
596              
597             sub _truncate
598             {
599 19851     19851   42735 my $self = shift;
600 19851         53082 my ($truncate_to) = @_;
601              
602 19851 100       79298 return unless defined $truncate_to;
603              
604 11945 100       35702 my @truncate_to = map { $_ =~ /_/ ? split /_/, $_ : $_ } $truncate_to;
  11945         94598  
605 11945         27433 my $i = 0;
606 11945         24710 my @units = @{$self->{data}->__units('ordered')};
  11945         113015  
607 11945         36397 my %indexes = map { $_ => $i++ } @units;
  95560         239648  
608 11945         45291 foreach my $unit (@truncate_to) {
609 20877         50393 my $index = $indexes{$unit} - 1;
610 20877 100 66     123600 if (defined $units[$index] && !exists $self->{modified}{$units[$index]}) {
611 11891         71950 $self->{datetime}->truncate(to => $unit);
612 11891         4497763 $self->_set_truncated;
613 11891         127421 last;
614             }
615             }
616             }
617              
618             sub _post_process
619             {
620 11743     11743   24427 my $self = shift;
621 11743         42872 my %opts = @_;
622              
623 11743         30284 delete $opts{truncate_to};
624              
625 11743 50 100     61785 if (($self->{Prefer_future} || $self->{Demand_future})
      66        
      66        
626             && (exists $opts{advance_future} && $opts{advance_future})
627             ) {
628 2070         50575 $self->_advance_future;
629             }
630             }
631              
632             sub _advance_future
633             {
634 2106     2106   4978 my $self = shift;
635 2106         6622 my %advance = map { $_ => true } @_;
  36         92  
636              
637 2106         4321 my %modified = map { $_ => true } keys %{$self->{modified}};
  6156         20652  
  2106         8459  
638             my $token_contains = sub
639             {
640 2520     2520   49895 my ($identifier) = @_;
641             return any {
642 27066         70011 my $data = $_;
643             any {
644 43158         70037 my $token = $_;
645 43158         302458 $token =~ /^$data$/i;
646 27066         63693 } @{$self->{tokens}}
  27066         61992  
647 2520         13445 } @{$self->{data}->{$identifier}};
  2520         14259  
648 2106         21484 };
649              
650             my $now = exists $self->{Datetime}
651             ? dclone($self->{Datetime})
652 2106 100       15952 : DateTime::HiRes->now(time_zone => $self->{Time_zone});
653              
654 2106     1530   1287933 my $day_of_week = sub { $_[0]->_Day_of_Week(map $_[0]->{datetime}->$_, qw(year month day)) };
  1530         66640  
655              
656 2106         9405 my $skip_weekdays = false;
657              
658 2106 50 33 4405   18495 if ((all { /^(?:(?:nano)?second|minute|hour)$/ } keys %modified)
  4405 100 66     33833  
    100 100        
    100 66        
    100 66        
    100 66        
      66        
      100        
      33        
      66        
      66        
      33        
      100        
      66        
      33        
      66        
      33        
      66        
      33        
659             && (exists $self->{modified}{hour} && $self->{modified}{hour} == 1)
660             && (($self->{Prefer_future} && $self->{datetime} < $now)
661             || ($self->{Demand_future} && $self->{datetime} <= $now))
662             ) {
663 234         30762 $self->{postprocess}{day} = 1;
664             }
665             elsif (sub {
666 1872 100   1872   33449 return false unless @{$self->{tokens}} == 2;
  1872         10379  
667 1398         13723 my ($day, $weekday) = map $self->{data}->__RE($_), qw(day weekday);
668 1398 100 100     16261 if ($self->{tokens}->[0] =~ $day
669             && $self->{tokens}->[1] =~ $weekday) {
670 36         146 $skip_weekdays = true;
671 36         152 return true;
672             }
673 1362         4920 return false;
674             }->()
675 108     108   1031 && (all { /^(?:day|month|year)$/ } keys %modified)
676             && (($self->{Prefer_future} && $self->{datetime}->day < $now->day)
677             || ($self->{Demand_future} && $self->{datetime}->day <= $now->day))
678             ) {
679 18         713 $self->{postprocess}{week} = 4;
680             }
681             elsif (($token_contains->('weekdays_all') && !$skip_weekdays)
682             && (exists $self->{modified}{day} && $self->{modified}{day} == 1)
683             && (($self->{Prefer_future} && $day_of_week->($self) < $now->wday)
684             || ($self->{Demand_future} && $day_of_week->($self) <= $now->wday))
685             ) {
686 1188         28774 $self->{postprocess}{day} = 7;
687             }
688             elsif (($token_contains->('months_all') || $advance{md})
689 156     156   2416 && (all { /^(?:day|month)$/ } keys %modified)
690             && (exists $self->{modified}{month} && $self->{modified}{month} == 1)
691             && (exists $self->{modified}{day}
692             ? $self->{modified}{day} == 1
693             ? true : false
694             : true)
695             && (($self->{Prefer_future} && $self->{datetime}->day_of_year < $now->day_of_year)
696             || ($self->{Demand_future} && $self->{datetime}->day_of_year <= $now->day_of_year))
697             ) {
698 72         4468 $self->{postprocess}{year} = 1;
699             }
700             }
701              
702             sub _token
703             {
704 857060     857060   1371521 my $self = shift;
705 857060         1525991 my ($pos) = @_;
706              
707 857060         1389116 my $str = '';
708 857060         1982243 my $token = $self->{tokens}->[0 + $pos];
709              
710 857060 50       6187411 return defined $token
711             ? \$token
712             : \$str;
713             }
714              
715 19851     19851   40023 sub _register_trace { push @{$_[0]->{trace}}, (caller(1))[3] }
  19851         192830  
716 11935     11935   26212 sub _unset_trace { @{$_[0]->{trace}} = () }
  11935         48646  
717              
718 2652     2652   26306 sub _get_error { $_[0]->{error} }
719 372     372   1325 sub _set_error { $_[0]->{error} = $_[1] }
720 12146     12146   35038 sub _unset_error { $_[0]->{error} = undef }
721              
722 12768     12768   156278 sub _get_failure { $_[0]->{failure} }
723 6     6   25 sub _set_failure { $_[0]->{failure} = true }
724 12146     12146   44618 sub _unset_failure { $_[0]->{failure} = false }
725              
726 13131     13131   71197 sub _get_valid_exp { $_[0]->{valid_expression} }
727 12098     12098   55975 sub _set_valid_exp { $_[0]->{valid_expression} = true }
728 12468     12468   34313 sub _unset_valid_exp { $_[0]->{valid_expression} = false }
729              
730 10980     10980   254096 sub _get_truncated { $_[0]->{truncated} }
731 12359     12359   53876 sub _set_truncated { $_[0]->{truncated} = true }
732 12256     12256   33240 sub _unset_truncated { $_[0]->{truncated} = false }
733              
734             sub _get_datetime_object
735             {
736 11935     11935   26296 my $self = shift;
737              
738             my $dt = DateTime->new(
739             time_zone => $self->{datetime}->time_zone,
740             year => $self->{datetime}->year,
741             month => $self->{datetime}->month,
742             day => $self->{datetime}->day_of_month,
743             hour => $self->{datetime}->hour,
744             minute => $self->{datetime}->minute,
745             second => $self->{datetime}->second,
746             nanosecond => $self->{datetime}->nanosecond,
747 11935         58057 );
748              
749 11935         4982522 foreach my $unit (keys %{$self->{postprocess}}) {
  11935         67025  
750 1512         9413 $dt->add("${unit}s" => $self->{postprocess}{$unit});
751             }
752              
753 11935         1884940 return $dt;
754             }
755              
756             # solely for testing purpose
757             sub _set_datetime
758             {
759 8016     8016   67657 my $self = shift;
760 8016         25989 my ($time, $tz) = @_;
761              
762 8016   100     88686 $self->{datetime_test} = DateTime->new(
763             time_zone => $tz || 'floating',
764             %$time,
765             );
766 8016         4113304 $self->{running_tests} = true;
767             }
768              
769             1;
770             __END__
771              
772             =encoding ISO8859-1
773              
774             =head1 NAME
775              
776             DateTime::Format::Natural - Parse informal natural language date/time strings
777              
778             =head1 SYNOPSIS
779              
780             use DateTime::Format::Natural;
781              
782             $parser = DateTime::Format::Natural->new;
783              
784             $dt = $parser->parse_datetime($date_string);
785             @dt = $parser->parse_datetime_duration($date_string);
786              
787             $date_string = $parser->extract_datetime($extract_string);
788             @date_strings = $parser->extract_datetime($extract_string);
789              
790             if ($parser->success) {
791             # operate on $dt/@dt, for example:
792             print $dt->strftime('%d.%m.%Y %H:%M:%S'), "\n";
793             } else {
794             warn $parser->error;
795             }
796              
797             @traces = $parser->trace;
798              
799             # examples
800              
801             12:14 PM
802             next tuesday at 2am
803             tomorrow morning
804             4pm yesterday
805             10 weeks ago
806              
807             1st tuesday last november
808             2nd friday in august
809             final thursday in april
810              
811             for 3 hours
812             monday to friday
813             1 April 10 am to 1 May 8am
814              
815             jan 24, 2011 12:00
816              
817             =head1 DESCRIPTION
818              
819             C<DateTime::Format::Natural> parses informal natural language date/time strings.
820             In addition, parsable date/time substrings may be extracted from ordinary strings.
821              
822             =head1 CONSTRUCTOR
823              
824             =head2 new
825              
826             Creates a new C<DateTime::Format::Natural> object. Arguments to C<new()> are options and
827             not necessarily required.
828              
829             $parser = DateTime::Format::Natural->new(
830             datetime => DateTime->new(...),
831             lang => 'en',
832             format => 'mm/dd/yy',
833             prefer_future => [0|1],
834             demand_future => [0|1],
835             time_zone => 'floating',
836             daytime => { morning => 06,
837             afternoon => 13,
838             evening => 20,
839             },
840             );
841              
842             =over 4
843              
844             =item * C<datetime>
845              
846             Overrides the present now with a L<DateTime> object provided.
847              
848             =item * C<lang>
849              
850             Contains the language selected, currently limited to C<en> (english).
851             Defaults to 'C<en>'.
852              
853             =item * C<format>
854              
855             Specifies the format of numeric dates.
856              
857             The format is used to influence how numeric dates are parsed. Given two
858             numbers separated by a slash, the month/day order expected comes from
859             this option. If there is a third number, this option describes where
860             to expect the year. When this format can't be used to interpret the
861             date, some unambiguous dates may be parsed, but there is no form
862             guarantee.
863              
864             Current supported "month/day" formats: C<dd/mm>, C<mm/dd>.
865              
866             Current supported "year/month/day" formats (with slashes): C<dd/mm/yy>,
867             C<dd/mm/yyyy>, C<mm/dd/yyyy>, C<yyyy/mm/dd>.
868              
869             Note that all of the above formats with three units do also parse
870             with dots or dashes as format separators.
871              
872             Furthermore, formats can be abbreviated as long as they remain
873             unambiguous.
874              
875             Defaults to 'C<d/m/y>'.
876              
877             =item * C<prefer_future>
878              
879             Prefers future time and dates. Accepts a boolean, defaults to false.
880              
881             =item * C<demand_future>
882              
883             Demands future time and dates. Similar to C<prefer_future>, but stronger.
884             Accepts a boolean, defaults to false.
885              
886             =item * C<time_zone>
887              
888             The time zone to use when parsing and for output. Accepts any time zone
889             recognized by L<DateTime>. Defaults to 'floating'.
890              
891             =item * C<daytime>
892              
893             A hash reference consisting of customized daytime hours,
894             which may be selectively changed.
895              
896             =back
897              
898             =head1 METHODS
899              
900             =head2 parse_datetime
901              
902             Returns a L<DateTime> object constructed from a natural language date/time string.
903              
904             $dt = $parser->parse_datetime($date_string);
905             $dt = $parser->parse_datetime(string => $date_string);
906              
907             =over 4
908              
909             =item * C<string>
910              
911             The date string.
912              
913             =back
914              
915             =head2 parse_datetime_duration
916              
917             Returns one or two L<DateTime> objects constructed from a natural language
918             date/time string which may contain timespans/durations. I<Same> interface
919             and options as C<parse_datetime()>, but should be explicitly called in
920             list context.
921              
922             @dt = $parser->parse_datetime_duration($date_string);
923             @dt = $parser->parse_datetime_duration(string => $date_string);
924              
925             =head2 extract_datetime
926              
927             Returns parsable date/time substrings (also known as expressions) extracted
928             from the string provided; in scalar context only the first parsable substring
929             is returned, whereas in list context all parsable substrings are returned.
930             Each extracted substring can then be passed to the C<parse_datetime()>/
931             C<parse_datetime_duration()> methods.
932              
933             $date_string = $parser->extract_datetime($extract_string);
934             @date_strings = $parser->extract_datetime($extract_string);
935             # or
936             $date_string = $parser->extract_datetime(string => $extract_string);
937             @date_strings = $parser->extract_datetime(string => $extract_string);
938              
939             =head2 success
940              
941             Returns a boolean indicating success or failure for parsing the date/time
942             string given.
943              
944             =head2 error
945              
946             Returns the error message if the parsing did not succeed.
947              
948             =head2 trace
949              
950             Returns one or two strings with the grammar keyword for the valid
951             expression parsed, traces of methods which were called within the Calc
952             class and a summary how often certain units have been modified. More than
953             one string is commonly returned for durations. Useful as a debugging aid.
954              
955             =head1 GRAMMAR
956              
957             The grammar handling has been rewritten to be easily extendable and hence
958             everybody is encouraged to propose sensible new additions and/or changes.
959              
960             See the class L<DateTime::Format::Natural::Lang::EN> if you're intending
961             to hack a bit on the grammar guts.
962              
963             =head1 EXAMPLES
964              
965             See the class L<DateTime::Format::Natural::Lang::EN> for an overview of
966             currently valid input.
967              
968             =head1 BUGS & CAVEATS
969              
970             C<parse_datetime()>/C<parse_datetime_duration()> always return one or two
971             DateTime objects regardless whether the parse was successful or not. In
972             case no valid expression was found or a failure occurred, an unaltered
973             DateTime object with its initial values (most often the "current" now) is
974             likely to be returned. It is therefore recommended to use C<success()> to
975             assert that the parse did succeed (at least, for common uses), otherwise
976             the absence of a parse failure cannot be guaranteed.
977              
978             C<parse_datetime()> is not capable of handling durations.
979              
980             =head1 CREDITS
981              
982             Thanks to Tatsuhiko Miyagawa for the initial inspiration. See Miyagawa's journal
983             entry L<http://use.perl.org/~miyagawa/journal/31378> for more information.
984              
985             Furthermore, thanks to (in order of appearance) who have contributed
986             valuable suggestions and patches:
987              
988             Clayton L. Scott
989             Dave Rolsky
990             CPAN Author 'SEKIMURA'
991             mike (pulsation)
992             Mark Stosberg
993             Tuomas Jormola
994             Cory Watson
995             Urs Stotz
996             Shawn M. Moore
997             Andreas J. König
998             Chia-liang Kao
999             Jonny Schulz
1000             Jesse Vincent
1001             Jason May
1002             Pat Kale
1003             Ankur Gupta
1004             Alex Bowley
1005             Elliot Shank
1006             Anirvan Chatterjee
1007             Michael Reddick
1008             Christian Brink
1009             Giovanni Pensa
1010             Andrew Sterling Hanenkamp
1011             Eric Wilhelm
1012             Kevin Field
1013             Wes Morgan
1014             Vladimir Marek
1015             Rod Taylor
1016             Tim Esselens
1017             Colm Dougan
1018             Chifung Fan
1019             Xiao Yafeng
1020             Roman Filippov
1021             David Steinbrunner
1022             Debian Perl Group
1023             Tim Bunce
1024             Ricardo Signes
1025             Felix Ostmann
1026             Jörn Clausen
1027             Jim Avera
1028             Olaf Alders
1029             Karen Etheridge
1030             Graham Ollis
1031             isla w
1032             gibus
1033              
1034             =head1 SEE ALSO
1035              
1036             L<dateparse>, L<DateTime>, L<Date::Calc>, L<http://datetime.perl.org>
1037              
1038             =head1 AUTHOR
1039              
1040             Steven Schubiger <schubiger@cpan.org>
1041              
1042             =head1 LICENSE
1043              
1044             This program is free software; you may redistribute it and/or
1045             modify it under the same terms as Perl itself.
1046              
1047             See L<http://dev.perl.org/licenses/>
1048              
1049             =cut