File Coverage

blib/lib/DateTime/Format/Duration.pm
Criterion Covered Total %
statement 273 316 86.3
branch 102 164 62.2
condition 37 47 78.7
subroutine 29 30 96.6
pod 12 19 63.1
total 453 576 78.6


line stmt bran cond sub pod time code
1             package DateTime::Format::Duration; # git description: v1.03a-16-g3f2a121
2             # ABSTRACT: Format and parse DateTime::Durations
3              
4 8     8   1078366 use Params::Validate qw( validate SCALAR OBJECT ARRAYREF HASHREF UNDEF );
  8         8976  
  8         722  
5 8     8   47 use Carp;
  8         14  
  8         488  
6 8     8   743 use DateTime::Duration;
  8         130177  
  8         209  
7              
8              
9 8     8   42 use constant MAX_NANOSECONDS => 1000000000; # 1E9 = almost 32 bits
  8         12  
  8         503  
10 8     8   39 use strict;
  8         13  
  8         44505  
11              
12             require Exporter;
13             our @ISA = qw/Exporter/;
14             our @EXPORT_OK = qw/strpduration strfduration/;
15             our %EXPORT_TAGS = (ALL => [qw/strpduration strfduration/]);
16              
17             our $VERSION = '1.04';
18              
19             #---------------------------------------------------------------------------
20             # CONSTRUCTORS
21             #---------------------------------------------------------------------------
22              
23             sub new {
24 57     57 1 14576 my $class = shift;
25 57         1640 my %args = validate( @_, {
26             pattern => { type => SCALAR, optional => 1 },
27             base => { type => OBJECT | UNDEF, default => undef },
28             normalise => { type => SCALAR, default => 0 },
29             normalize => { type => SCALAR, default => 0 },
30             });
31              
32 57   66     536 $args{normalise} ||= delete $args{normalize};
33 57 100       139 $args{normalise} = 1 if $args{base};
34              
35 57         371 return bless \%args, $class;
36             }
37              
38              
39             #---------------------------------------------------------------------------
40             # SETTERS AND ACCESSORS
41             #---------------------------------------------------------------------------
42              
43 298 50   298 1 597 sub pattern { croak("No arguments should be passed to pattern. Use set_pattern() instead.") if $_[1]; $_[0]->{pattern} or undef }
  298 50       10544  
44             sub set_pattern {
45 9     9 1 59 my $self = shift;
46 9         16 my $newpattern = shift;
47 9         21 $self->{parser} = '';
48 9         15 $self->{pattern} = $newpattern;
49 9         18 return $self;
50             }
51              
52 363 50   363 1 940 sub base { croak("No arguments should be passed to base. Use set_base() instead.") if $_[1]; $_[0]->{base} or undef }
  363 100       1572  
53             sub set_base {
54 3     3 1 2346 my $self = shift;
55 3         3 my $newbase = shift;
56 3 50       14 croak("Argument to set_base() must be a DateTime object.") unless ref($newbase) eq 'DateTime';
57 3         8 $self->{base} = $newbase;
58 3         16 return $self;
59             }
60              
61 239 50   239 1 474 sub normalising { croak("No arguments should be passed to normalising. Use set_normalising() instead.") if $_[1]; ($_[0]->{normalise}) ? 1 : 0 }
  239 100       1206  
62             *normalizing = \&normalising; *normalizing = \&normalising;
63             sub set_normalising {
64 0     0 1 0 my $self = shift;
65 0         0 my $new = shift;
66 0 0       0 $self->{normalise} = ($new) ? 1 : 0;
67 0         0 return $self;
68             }
69             *set_normalizing = \&set_normalising; *set_normalizing = \&set_normalising;
70              
71              
72             #---------------------------------------------------------------------------
73             # DATA
74             #---------------------------------------------------------------------------
75              
76              
77             my %formats =
78             ( 'C' => sub { int( $_[0]->{years} / 100 ) },
79             'd' => sub { sprintf( '%02d', $_[0]->{days} ) },
80             'e' => sub { sprintf( '%d', $_[0]->{days} ) },
81             'F' => sub { sprintf( '%04d-%02d-%02d', $_[0]->{years}, $_[0]->{months}, $_[0]->{days} ) },
82             'H' => sub { sprintf( '%02d', $_[0]->{hours} ) },
83             'I' => sub { sprintf( '%02d', $_[0]->{hours} ) },
84             'j' => sub { $_[1]->as_days($_[0]) },
85             'k' => sub { sprintf( '%2d', $_[0]->{hours} ) },
86             'l' => sub { sprintf( '%2d', $_[0]->{hours} ) },
87             'm' => sub { sprintf( '%02d', $_[0]->{months} ) },
88             'M' => sub { sprintf( '%02d', $_[0]->{minutes} ) },
89             'n' => sub { "\n" }, # should this be OS-sensitive?"
90             'N' => sub { _format_nanosecs(@_) },
91             'p' => sub { ($_[0]->{negative}) ? '-' : '+' },
92             'P' => sub { ($_[0]->{negative}) ? '-' : '' },
93             'r' => sub { sprintf('%02d:%02d:%02d', $_[0]->{hours}, $_[0]->{minutes}, $_[0]->{seconds} ) },
94             'R' => sub { sprintf('%02d:%02d', $_[0]->{hours}, $_[0]->{minutes}) },
95             's' => sub { $_[1]->as_seconds($_[0]) },
96             'S' => sub { sprintf( '%02d', $_[0]->{seconds} ) },
97             't' => sub { "\t" }, #"
98             'T' => sub { sprintf('%s%02d:%02d:%02d', ($_[0]->{negative}) ? '-' : '', $_[0]->{hours}, $_[0]->{minutes}, $_[0]->{seconds} ) },
99             'u' => sub { $_[1]->as_days($_[0]) % 7 },
100             'V' => sub { $_[1]->as_weeks($_[0]) },
101             'W' => sub { int(($_[1]->as_seconds($_[0]) / (60*60*24*7))*1_000_000_000) / 1_000_000_000 },
102             'y' => sub { sprintf( '%02d', substr( $_[0]->{years}, -2 ) ) },
103             'Y' => sub { return $_[0]->{years} },
104             '%' => sub { '%' },
105             );
106              
107              
108             #---------------------------------------------------------------------------
109             # METHODS
110             #---------------------------------------------------------------------------
111              
112             sub format_duration {
113 31     31 1 307 my $self = shift;
114              
115 31         34 my $duration;
116             my @formats;
117              
118 31 50       63 if ( scalar(@_) == 1 ) {
119 31         36 $duration = shift;
120 31 50       59 @formats = ($self->pattern) if $self->pattern;
121             } else {
122 0         0 my %args = validate( @_, {
123             pattern => { type => SCALAR | ARRAYREF, default => $self->pattern },
124             duration => { type => OBJECT },
125             });
126 0         0 $duration = $args{duration};
127 0 0       0 @formats = ref($args{pattern}) ? @{$args{pattern}} : ($args{pattern});
  0         0  
128             }
129              
130 31 50       70 croak("No formats defined") unless @formats;
131              
132 31 100       68 my %duration = ($self->normalising)
133             ? $self->normalise( $duration )
134             : $duration->deltas;
135              
136 31         508 return $self->format_duration_from_deltas(
137             pattern => [@formats],
138             %duration
139             );
140             }
141              
142              
143             sub format_duration_from_deltas {
144 208     208 1 310 my $self = shift;
145              
146 208         578 my %args = validate( @_, {
147             pattern => { type => SCALAR | ARRAYREF, default => $self->pattern },
148             negative => { type => SCALAR, default => 0 },
149             years => { type => SCALAR, default => 0 },
150             months => { type => SCALAR, default => 0 },
151             days => { type => SCALAR, default => 0 },
152             hours => { type => SCALAR, default => 0 },
153             minutes => { type => SCALAR, default => 0 },
154             seconds => { type => SCALAR, default => 0 },
155             nanoseconds => { type => SCALAR, default => 0 },
156             });
157              
158 208 100       2540 my @formats = ref($args{pattern}) ? @{$args{pattern}} : ($args{pattern});
  31         75  
159 208         322 delete $args{pattern};
160 208 100       448 my %duration = ($self->normalising)
161             ? $self->normalise( %args )
162             : %args;
163              
164 208         427 my @r;
165 208         403 foreach my $f (@formats)
166             {
167             # regex from Date::Format - thanks Graham!
168 208         954 $f =~ s/
169             %(\d*)([%a-zA-MO-Z]) # N returns from the left rather than the right
170             /
171             $formats{$2}
172             ? ($1)
173             ? sprintf("%0$1d", substr($formats{$2}->(\%duration, $self),$1*-1) )
174 610 50       3164 : $formats{$2}->(\%duration, $self)
    50          
175             : $1
176              
177             /sgex;
178              
179             # %3N
180 208         716 $f =~ s/
181             %(\d*)N
182             /
183 27         61 $formats{N}->(\%duration, $1)
184             /sgex;
185              
186 208 50       1824 return $f unless wantarray;
187              
188 0         0 push @r, $f;
189             }
190              
191 0         0 return @r;
192             }
193              
194              
195             sub parse_duration {
196 24     24 1 31 my $self = shift;
197 24         51 DateTime::Duration->new(
198             $self->parse_duration_as_deltas(@_)
199             );
200             }
201              
202             sub parse_duration_as_deltas {
203 196     196 1 61016 my ( $self, $time_string ) = @_;
204              
205 196         598 local $^W = undef;
206              
207             # Variables from the parser
208 196         213 my ( $centuries,$years, $months,
209             $weeks, $days, $hours,
210             $minutes, $seconds, $nanoseconds
211             );
212              
213             # Variables for DateTime
214 196         279 my ( $Years, $Months, $Days,
215             $Hours, $Minutes, $Seconds, $Nanoseconds,
216             ) = ();
217              
218             # Run the parser
219 196   66     547 my $parser = $self->{parser} || $self->_build_parser;
220 196         20142 eval($parser);
221 196 50       786 die "Parser ($parser) died:$@" if $@;
222              
223 196         414 $years += ($centuries * 100);
224 196         283 $days += ($weeks * 7 );
225              
226             return (
227 196   100     3154 years => $years || 0,
      100        
      100        
      100        
      100        
      100        
      100        
228             months => $months || 0,
229             days => $days || 0,
230             hours => $hours || 0,
231             minutes => $minutes || 0,
232             seconds => $seconds || 0,
233             nanoseconds => $nanoseconds || 0,
234             );
235              
236             }
237              
238              
239             #---------------------------------------------------------------------------
240             # UTILITY FUNCTIONS
241             #---------------------------------------------------------------------------
242              
243             sub normalise {
244 197     197 1 268 my $self = shift;
245              
246             return $self->normalise_no_base(@_)
247             if (
248 197 100 33     809 ($self->{normalising} and $self->{normalising} =~ /^ISO$/i)
      66        
249             or not $self->base
250             );
251              
252 69 100       4037 my %delta = (ref($_[0]) =~/^DateTime::Duration/)
253             ? $_[0]->deltas
254             : @_;
255              
256 69 50       241 if (delete $delta{negative}) {
257 0         0 foreach (keys %delta) { $delta{$_} *= -1 }
  0         0  
258             }
259              
260 69 50       157 if ($self->{diagnostic}) {require Data::Dumper; print 'Pre Normalise: ' . Data::Dumper::Dumper( \%delta );}
  0         0  
  0         0  
261              
262 69         147 my $start = $self->base->clone;
263 69         2872 my $end = $self->base->clone;
264             # Can't just add the hash as ->add(%delta) because of mixed positivity:
265 69         2834 foreach (qw/years months days hours minutes seconds nanoseconds/) {
266 483   100     2178 $end->add( $_ => $delta{$_}||0 );
267 483 50       95895 print "Adding $delta{$_} $_: " . $end->datetime . "\n" if $self->{diagnostic};
268             }
269              
270              
271 69         101 my %new_delta;
272 69         83 my $set_negative = 0;
273 69 100       214 if ($start > $end) {
274 14         888 ($start, $end) = ($end, $start);
275 14         25 $set_negative = 1;
276             }
277              
278             # Creeping method:
279 69         3702 $new_delta{years} = $end->year - $start->year;
280 69 50       639 printf("Adding %d years: %s\n", $new_delta{years}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
281              
282 69         201 $new_delta{months} = $end->month - $start->month;
283 69 50       500 printf("Adding %d months: %s\n", $new_delta{months}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
284              
285 69         185 $new_delta{days} = $end->day - $start->day;
286 69 50       494 printf("Adding %d days: %s\n", $new_delta{days}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
287              
288 69         173 $new_delta{hours} = $end->hour - $start->hour;
289 69 50       493 printf("Adding %d hours: %s\n", $new_delta{hours}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
290              
291 69         179 $new_delta{minutes} = $end->minute - $start->minute;
292 69 50       538 printf("Adding %d minutes: %s\n", $new_delta{minutes}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
293              
294 69         162 $new_delta{seconds} = $end->second - $start->second;
295 69 50       513 printf("Adding %d seconds: %s\n", $new_delta{seconds}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
296              
297 69         168 $new_delta{nanoseconds} = $end->nanosecond - $start->nanosecond;
298 69 50       534 printf("Adding %d nanoseconds: %s\n", $new_delta{nanoseconds}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
299              
300 69 50       154 if( $new_delta{nanoseconds} < 0 ){
301 0         0 $new_delta{nanoseconds} += MAX_NANOSECONDS;
302 0         0 $new_delta{seconds}--;
303 0 0       0 printf("Oops: Adding %d nanoseconds, %d seconds: %s\n", $new_delta{nanoseconds}, $new_delta{seconds}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
304             }
305              
306 69 100       148 if( $new_delta{seconds} < 0 ){
307 4         15 $new_delta{seconds} += $end->clone->truncate( to => 'minute' )->subtract( seconds => 1 )->second + 1;
308 4         5004 $new_delta{minutes}--;
309 4 50       56 printf("Oops: Adding %d seconds, %d minutes: %s\n", $new_delta{seconds}, $new_delta{minutes}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
310             }
311              
312 69 100       143 if( $new_delta{minutes} < 0 ){
313 10         14 $new_delta{minutes} += 60;
314 10         14 $new_delta{hours}--;
315 10 50       46 printf("Oops: Adding %d minutes, %d hours: %s\n", $new_delta{minutes}, $new_delta{hours}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
316             }
317              
318 69 100       136 if( $new_delta{hours} < 0 ){
319 11         40 $new_delta{hours} += _hours_in_day($end->clone->truncate( to => 'day' )->subtract( seconds => 5 ));
320 11         13598 $new_delta{days}--;
321 11 50       35 printf("Oops: Adding %d hours, %d days: %s\n", $new_delta{hours}, $new_delta{days}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
322             }
323              
324 69 100       142 if( $new_delta{days} < 0 ){
325             # Thought this was correct .. I was wrong, but I want to leave it here anyway
326             # $new_delta{days} += $end->clone->truncate( to => 'month' )->subtract( seconds => 5 )->day;
327 16         51 $new_delta{days} += $start->clone->truncate( to => 'month' )->add(months => 1)->subtract( seconds => 5 )->day;
328 16         27038 $new_delta{months}--;
329 16 50       51 printf("Oops: Adding %d days, %d months: %s\n", $new_delta{days}, $new_delta{months}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
330             }
331              
332 69 100       135 if( $new_delta{months} < 0 ){
333 14         18 $new_delta{months} += 12;
334 14         19 $new_delta{years}--;
335 14 50       35 printf("Oops: Adding %d months, %d years: %s\n", $new_delta{months}, $new_delta{years}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
336             }
337              
338 69         122 $new_delta{negative} = $set_negative;
339              
340 69 50       152 if ($self->{diagnostic}) {require Data::Dumper; print 'Post Normalisation: ' . Data::Dumper::Dumper( \%new_delta );}
  0         0  
  0         0  
341              
342 69         712 return %new_delta
343             }
344             *normalize = \&normalise;
345             *normalize = \&normalise;
346              
347             sub normalise_no_base {
348 128     128 0 153 my $self = shift;
349 128 100       572 my %delta = (ref($_[0]) =~/^DateTime::Duration/) ? $_[0]->deltas : @_;
350              
351 128 100       558 if (delete $delta{negative}) {
352 1         4 foreach (keys %delta) { $delta{$_} *= -1 }
  7         11  
353             }
354 128         270 foreach(qw/years months days hours minutes seconds nanoseconds/) {
355 896   100     2374 $delta{$_} ||= 0;
356             }
357              
358 128 50       261 if ($self->{diagnostic}) {
359 0         0 require Data::Dumper;
360 0         0 print 'Pre Baseless Normalise: ' . Data::Dumper::Dumper( \%delta );
361             }
362              
363             # Remove any decimals:
364 128         284 $delta{nanoseconds} += (MAX_NANOSECONDS * ($delta{seconds} - int($delta{seconds})));
365 128         164 $delta{seconds} = int($delta{seconds});
366 128         189 $delta{seconds} += (60 * ($delta{minutes} - int($delta{minutes})));
367 128         158 $delta{minutes} = int($delta{minutes});
368 128         185 $delta{minutes} += (60 * ($delta{hours} - int($delta{hours})));
369 128         160 $delta{hours} = int($delta{hours});
370 128         178 $delta{hours} += (24 * ($delta{days} - int($delta{days})));
371 128         140 $delta{days} = int($delta{days});
372 128         181 $delta{days} += (30 * ($delta{months} - int($delta{months})));
373 128         150 $delta{months} = int($delta{months});
374              
375 128         246 ($delta{nanoseconds}, $delta{seconds}) = _set_max($delta{nanoseconds}, MAX_NANOSECONDS, $delta{seconds});
376 128         252 ($delta{seconds}, $delta{minutes}) = _set_max($delta{seconds}, 60, $delta{minutes});
377 128         236 ($delta{minutes}, $delta{hours}) = _set_max($delta{minutes}, 60, $delta{hours} );
378 128         238 ($delta{hours}, $delta{days}) = _set_max($delta{hours}, 24, $delta{days} );
379             ($delta{days}, $delta{months}) = _set_max($delta{days}, 30, $delta{months} )
380 128 100       374 if $self->{normalise} =~ /^iso$/i;
381 128         230 ($delta{months}, $delta{years}) = _set_max($delta{months}, 12, $delta{years} );
382              
383 128 50       262 if ($self->{diagnostic}) {
384 0         0 require Data::Dumper;
385 0         0 print 'Post Baseless Normalise: ' . Data::Dumper::Dumper( \%delta );
386             }
387              
388 128         357 %delta = _denegate( %delta );
389              
390 128 50       433 if ($self->{diagnostic}) {
391 0         0 require Data::Dumper;
392 0         0 print 'Post Denegation: ' . Data::Dumper::Dumper( \%delta );
393             }
394              
395 128         637 return %delta;
396             }
397             *normalize_no_base = \&normalise_no_base;
398             *normalize_no_base = \&normalise_no_base;
399              
400             sub as_weeks {
401 2     2 0 3 my $self = shift;
402 2         6 return int($self->as_seconds($_[0]) / (7*24*60*60));
403             }
404              
405             sub as_days {
406 3     3 0 5 my $self = shift;
407 3         8 return int($self->as_seconds($_[0]) / (24*60*60));
408             }
409              
410             sub as_seconds {
411 7     7 0 10 my $self = shift;
412              
413 7 50       16 my %delta = (ref($_[0])) ? %{$_[0]} : @_;
  7         31  
414 7 50       22 if (delete $delta{negative}) {foreach( keys %delta ) { $delta{$_} *= -1 }};
  0         0  
  0         0  
415              
416 7 50       17 unless ($self->base) {
417 0         0 my $seconds = $delta{nanoseconds} / MAX_NANOSECONDS;
418 0         0 $seconds += $delta{seconds};
419 0         0 $seconds += $delta{minutes} * 60;
420 0         0 $seconds += $delta{hours} * (60*60);
421 0         0 $seconds += $delta{days} * (24*60*60);
422 0         0 $seconds += $delta{months} * (30*24*60*60);
423 0         0 $seconds += $delta{years} * (12*30*24*60*60);
424 0         0 return $seconds;
425             }
426              
427 7         432 my $dt1 = $self->base + DateTime::Duration->new( %delta );
428             return int(($dt1->{utc_rd_days} - $self->base->{utc_rd_days}) * (24*60*60))
429 7         4111 + ($dt1->{utc_rd_secs} - $self->base->{utc_rd_secs});
430             }
431              
432              
433             sub debug_level{
434 48     48 0 56 my $self = shift;
435 48         48 my $level = shift;
436 48 50       83 if ($level > 0) {
437 0         0 Params::Validate::validation_options(
438             on_fail => \&Carp::confess,
439             );
440             } else {
441 48         114 Params::Validate::validation_options(
442             on_fail => undef,
443             );
444             }
445 48 50       944 $self->{diagnostic} = ($level) ? $level-1 : 0;
446             }
447              
448              
449              
450             #---------------------------------------------------------------------------
451             # EXPORTABLE FUNCTIONS
452             #---------------------------------------------------------------------------
453              
454             sub strfduration { #format
455 24     24 0 2574 my %args = validate( @_, {
456             pattern => { type => SCALAR | ARRAYREF },
457             duration => { type => OBJECT },
458             normalise => { type => SCALAR, optional => 1 },
459             base => { type => OBJECT, optional => 1 },
460             debug => { type => SCALAR, default => 0 },
461             });
462             my $new = DateTime::Format::Duration->new(
463             pattern => $args{pattern},
464             base => $args{base},
465             normalise=> $args{normalise},
466 24         239 );
467 24         61 $new->debug_level( $args{debug } );
468 24         54 return $new->format_duration( $args{duration} );
469             }
470              
471             sub strpduration { #parse
472 24     24 0 1243 my %args = validate( @_, {
473             pattern => { type => SCALAR | ARRAYREF },
474             duration => { type => SCALAR },
475             base => { type => OBJECT, optional => 1 },
476             as_deltas => { type => SCALAR, default => 0 },
477             debug => { type => SCALAR, default => 0 },
478             });
479             my $new = DateTime::Format::Duration->new(
480             pattern => $args{pattern},
481             base => $args{base},
482 24         261 );
483 24         68 $new->debug_level( $args{debug} );
484 24 50       82 return $new->parse_duration( $args{duration} ) unless $args{as_deltas};
485              
486 0         0 return $new->parse_duration_as_deltas( $args{duration} );
487             }
488              
489              
490              
491             #---------------------------------------------------------------------------
492             # INTERNAL FUNCTIONS
493             #---------------------------------------------------------------------------
494              
495             sub _format_nanosecs {
496 27     27   32 my %deltas = %{+shift};
  27         113  
497 27         60 my $precision = shift;
498              
499 27         61 my $ret = sprintf( "%09d", $deltas{nanoseconds} );
500 27 100       136 return $ret unless $precision; # default = 9 digits
501              
502 2         11 my ( $int, $frac ) = split(/[.,]/, $deltas{nanoseconds});
503 2 50       7 $ret .= $frac if $frac;
504              
505 2         13 return substr( $ret, 0, $precision );
506             }
507              
508             sub _build_parser {
509 28     28   40 my $self = shift;
510 28   33     93 my $regex = my $field_list = shift || $self->pattern;
511 28         181 my @fields = $field_list =~ m/(%\{\w+\}|%\d*.)/g;
512 28         58 $field_list = join('',@fields);
513              
514 28         108 my $tempdur = DateTime::Duration->new( seconds => 0 ); # Created just so we can do $tempdt->can(..)
515              
516             # I'm absoutely certain there's a better way to do this:
517 28         1743 $regex=~s|([\/\.\-])|\\$1|g;
518              
519 28         83 $regex =~ s/%[Tr]/%H:%M:%S/g;
520 28         57 $field_list =~ s/%[Tr]/%H%M%S/g;
521             # %T is the time as %H:%M:%S.
522              
523 28         46 $regex =~ s/%R/%H:%M/g;
524 28         37 $field_list =~ s/%R/%H%M/g;
525             #is the time as %H:%M.
526              
527 28         70 $regex =~ s|%F|%Y\\-%m\\-%d|g;
528 28         42 $field_list =~ s|%F|%Y%m%d|g;
529             #is the same as %Y-%m-%d
530              
531             # Negative and Positive
532 28         40 $regex =~ s/%P/[+-]?/g;
533 28         40 $field_list =~ s/%P//g;#negative#/g;
534              
535              
536             # Numerated places:
537              
538             # Centuries:
539 28 50       41 $regex =~ s/%(\d*)[C]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  1         7  
540 28         42 $field_list =~ s/%(\d*)[C]/#centuries#/g;
541              
542             # Years:
543 28 50       226 $regex =~ s/%(\d*)[Yy]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  7         54  
544 28         61 $field_list =~ s/%(\d*)[Yy]/#years#/g;
545              
546             # Months:
547 28 50       57 $regex =~ s/%(\d*)[m]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  6         25  
548 28         53 $field_list =~ s/%(\d*)[m]/#months#/g;
549              
550             # Weeks:
551 28 50       50 $regex =~ s/%(\d*)[GV]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  1         6  
552 28         54 $field_list =~ s/%(\d*)[GV]/#weeks#/g;
553 28         39 $regex =~ s/%\d*[W]/" *([+-]?\\d+\\.?\\d*)"/eg;
  1         3  
554 28         39 $field_list =~ s/%\d*[W]/#weeks#/g;
555              
556             # Days:
557 28 50       57 $regex =~ s/%(\d*)[deju]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  9         64  
558 28         59 $field_list =~ s/%(\d*)[deju]/#days#/g;
559              
560             # Hours:
561 28 50       58 $regex =~ s/%(\d*)[HIkl]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  11         52  
562 28         61 $field_list =~ s/%(\d*)[HIkl]/#hours#/g;
563              
564             # Minutes:
565 28 50       53 $regex =~ s/%(\d*)[M]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  8         31  
566 28         56 $field_list =~ s/%(\d*)[M]/#minutes#/g;
567              
568             # Seconds:
569 28 50       57 $regex =~ s/%(\d*)[sS]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  8         31  
570 28         50 $field_list =~ s/%(\d*)[sS]/#seconds#/g;
571              
572             # Nanoseconds:
573 28 50       38 $regex =~ s/%(\d*)[N]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  1         5  
574 28         40 $field_list =~ s/%(\d*)[N]/#nanoseconds#/g;
575              
576              
577             # Any function in DateTime.
578 28 0       43 $regex =~ s|%\{(\w+)}|($tempdur->can($1)) ? "(.+)" : ".+"|eg;
  0         0  
579 28 0       38 $field_list =~ s|(%\{(\w+)})|($tempdur->can($2)) ? "#$2#" : $1 |eg;
  0         0  
580              
581             # White space:
582 28 50       41 $regex =~ s/%(\d*)[tn]/($1) ? "\\s{$1}" : "\\s+"/eg;
  2         9  
583 28         56 $field_list =~ s/%(\d*)[tn]//g;
584              
585             # is replaced by %.
586 28         38 $regex =~ s/%%/%/g;
587 28         62 $field_list =~ s/%%//g;
588              
589 28         169 $field_list=~s/#([a-z0-9_]+)#/\$$1, /gi;
590 28         92 $field_list=~s/,\s*$//;
591              
592 28 50       76 croak("Unknown symbols in parse: $1") if $field_list=~/(\%\w)/g;
593              
594 28         204 $self->{parser} = qq|($field_list) = \$time_string =~ /$regex/|;
595             }
596              
597             sub _set_max {
598             #$$_[0] should roll over to the next $$_[2] when it reaches $_[1]
599             #seconds should roll over to the next minute when it reaches 60.
600 680     680   881 my ($small, $max, $large) = @_;
601             #warn "$small should roll over to the next $large when it reaches $max\n";
602 680         914 $large += int($small / $max);
603 680 100       1140 $small = ($small < 0)
604             ? $small % -$max
605             : $small % $max;
606 680         1396 return ($small, $large);
607             }
608              
609             sub _denegate {
610 130     130   353 my %delta = @_;
611 130         162 my ($negatives, $positives);
612 130         193 foreach(qw/years months days hours minutes seconds nanoseconds/) {
613 910 100       2357 if ($delta{$_} < 0) {
    100          
614 35         71 $negatives++;
615             } elsif ($delta{$_} > 0) {
616 139         196 $positives++;
617             } # ignore == 0
618             }
619 130 100 100     525 if ($negatives and not $positives) {
    100 66        
620 22         32 foreach(qw/years months days hours minutes seconds nanoseconds/) {
621 154 100       283 if ($delta{$_} < 0) {
622 31         43 $delta{$_} *= -1
623             }
624 154   100     433 $delta{$_} ||= 0;
625             }
626 22         43 $delta{negative} = 1;
627             } elsif ($negatives and $positives) {
628             # Work to match largest component
629 2         5 my $make = '';
630 2         5 foreach(qw/years months days hours minutes seconds nanoseconds/) {
631 8 100       28 if ($delta{$_} < 0) {
    50          
632 2         4 $make = 'negative';
633 2         6 last;
634             } elsif ($delta{$_} > 0) {
635 0         0 $make = 'positive';
636 0         0 last;
637             }
638             }
639 2 50       6 if ($make) {
640 2         10 ($delta{seconds}, $delta{minutes}) = _make($make,$delta{seconds}, 60, $delta{minutes});
641 2         7 ($delta{minutes}, $delta{hours}) = _make($make,$delta{minutes}, 60, $delta{hours} );
642 2         6 ($delta{hours}, $delta{days}) = _make($make,$delta{hours}, 24, $delta{days} );
643 2         7 ($delta{months}, $delta{years}) = _make($make,$delta{months}, 12, $delta{years} );
644 2         20 %delta = _denegate(%delta);
645             }
646             }
647 130         858 return %delta
648             }
649              
650             sub _make {
651 8     8   14 my ($make, $small, $max, $large) = @_;
652 8   66     29 while ($small < 0 and $make eq 'positive') {
653 0         0 $small += $max;
654 0         0 $large -= 1;
655             }
656 8   66     26 while ($small > 0 and $make eq 'negative') {
657 2         4 $small -= $max;
658 2         5 $large += 1;
659             }
660 8         19 return ($small, $large);
661             }
662              
663             sub _hours_in_day{
664 11     11   10622 my $day = shift;
665              
666             return (
667 11         41 $day->clone->truncate( to => 'day' )->add( days => 1 )->epoch
668             -
669             $day->clone->truncate( to => 'day' )->epoch
670             ) / (60 * 60)
671              
672             }
673              
674             1;
675              
676             __END__