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;
2              
3 8     8   1867449 use Params::Validate qw( validate SCALAR OBJECT ARRAYREF HASHREF UNDEF );
  8         18285  
  8         872  
4 8     8   58 use Carp;
  8         18  
  8         710  
5 8     8   8887 use DateTime::Duration;
  8         304428  
  8         199  
6              
7              
8 8     8   53 use constant MAX_NANOSECONDS => 1000000000; # 1E9 = almost 32 bits
  8         17  
  8         560  
9 8     8   97 use strict;
  8         18  
  8         62398  
10              
11             require Exporter;
12             our @ISA = qw/Exporter/;
13             our @EXPORT_OK = qw/strpduration strfduration/;
14             our %EXPORT_TAGS = (ALL => [qw/strpduration strfduration/]);
15              
16             our $VERSION = '1.03';
17              
18             #---------------------------------------------------------------------------
19             # CONSTRUCTORS
20             #---------------------------------------------------------------------------
21              
22             sub new {
23 57     57 1 31826 my $class = shift;
24 57         7402 my %args = validate( @_, {
25             pattern => { type => SCALAR, optional => 1 },
26             base => { type => OBJECT | UNDEF, default => undef },
27             normalise => { type => SCALAR, default => 0 },
28             normalize => { type => SCALAR, default => 0 },
29             });
30              
31 57   66     871 $args{normalise} ||= delete $args{normalize};
32 57 100       139 $args{normalise} = 1 if $args{base};
33              
34 57         570 return bless \%args, $class;
35             }
36              
37              
38             #---------------------------------------------------------------------------
39             # SETTERS AND ACCESSORS
40             #---------------------------------------------------------------------------
41              
42 298 50   298 1 1111 sub pattern { croak("No arguments should be passed to pattern. Use set_pattern() instead.") if $_[1]; $_[0]->{pattern} or undef }
  298 50       18714  
43             sub set_pattern {
44 9     9 1 63 my $self = shift;
45 9         20 my $newpattern = shift;
46 9         28 $self->{parser} = '';
47 9         27 $self->{pattern} = $newpattern;
48 9         19 return $self;
49             }
50              
51 363 50   363 1 1334 sub base { croak("No arguments should be passed to base. Use set_base() instead.") if $_[1]; $_[0]->{base} or undef }
  363 100       3042  
52             sub set_base {
53 3     3 1 6458 my $self = shift;
54 3         6 my $newbase = shift;
55 3 50       15 croak("Argument to set_base() must be a DateTime object.") unless ref($newbase) eq 'DateTime';
56 3         8 $self->{base} = $newbase;
57 3         23 return $self;
58             }
59              
60 239 50   239 1 610 sub normalising { croak("No arguments should be passed to normalising. Use set_normalising() instead.") if $_[1]; ($_[0]->{normalise}) ? 1 : 0 }
  239 100       3298  
61             *normalizing = \&normalising; *normalizing = \&normalising;
62             sub set_normalising {
63 0     0 1 0 my $self = shift;
64 0         0 my $new = shift;
65 0 0       0 $self->{normalise} = ($new) ? 1 : 0;
66 0         0 return $self;
67             }
68             *set_normalizing = \&set_normalising; *set_normalizing = \&set_normalising;
69              
70              
71             #---------------------------------------------------------------------------
72             # DATA
73             #---------------------------------------------------------------------------
74              
75              
76             my %formats =
77             ( 'C' => sub { int( $_[0]->{years} / 100 ) },
78             'd' => sub { sprintf( '%02d', $_[0]->{days} ) },
79             'e' => sub { sprintf( '%d', $_[0]->{days} ) },
80             'F' => sub { sprintf( '%04d-%02d-%02d', $_[0]->{years}, $_[0]->{months}, $_[0]->{days} ) },
81             'H' => sub { sprintf( '%02d', $_[0]->{hours} ) },
82             'I' => sub { sprintf( '%02d', $_[0]->{hours} ) },
83             'j' => sub { $_[1]->as_days($_[0]) },
84             'k' => sub { sprintf( '%2d', $_[0]->{hours} ) },
85             'l' => sub { sprintf( '%2d', $_[0]->{hours} ) },
86             'm' => sub { sprintf( '%02d', $_[0]->{months} ) },
87             'M' => sub { sprintf( '%02d', $_[0]->{minutes} ) },
88             'n' => sub { "\n" }, # should this be OS-sensitive?"
89             'N' => sub { _format_nanosecs(@_) },
90             'p' => sub { ($_[0]->{negative}) ? '-' : '+' },
91             'P' => sub { ($_[0]->{negative}) ? '-' : '' },
92             'r' => sub { sprintf('%02d:%02d:%02d', $_[0]->{hours}, $_[0]->{minutes}, $_[0]->{seconds} ) },
93             'R' => sub { sprintf('%02d:%02d', $_[0]->{hours}, $_[0]->{minutes}) },
94             's' => sub { $_[1]->as_seconds($_[0]) },
95             'S' => sub { sprintf( '%02d', $_[0]->{seconds} ) },
96             't' => sub { "\t" }, #"
97             'T' => sub { sprintf('%s%02d:%02d:%02d', ($_[0]->{negative}) ? '-' : '', $_[0]->{hours}, $_[0]->{minutes}, $_[0]->{seconds} ) },
98             'u' => sub { $_[1]->as_days($_[0]) % 7 },
99             'V' => sub { $_[1]->as_weeks($_[0]) },
100             'W' => sub { int(($_[1]->as_seconds($_[0]) / (60*60*24*7))*1_000_000_000) / 1_000_000_000 },
101             'y' => sub { sprintf( '%02d', substr( $_[0]->{years}, -2 ) ) },
102             'Y' => sub { return $_[0]->{years} },
103             '%' => sub { '%' },
104             );
105              
106              
107             #---------------------------------------------------------------------------
108             # METHODS
109             #---------------------------------------------------------------------------
110              
111             sub format_duration {
112 31     31 1 397 my $self = shift;
113              
114 31         37 my $duration;
115             my @formats;
116              
117 31 50       69 if ( scalar(@_) == 1 ) {
118 31         37 $duration = shift;
119 31 50       71 @formats = ($self->pattern) if $self->pattern;
120             } else {
121 0         0 my %args = validate( @_, {
122             pattern => { type => SCALAR | ARRAYREF, default => $self->pattern },
123             duration => { type => OBJECT },
124             });
125 0         0 $duration = $args{duration};
126 0 0       0 @formats = ref($args{pattern}) ? @{$args{pattern}} : ($args{pattern});
  0         0  
127             }
128              
129 31 50       75 croak("No formats defined") unless @formats;
130              
131 31 100       74 my %duration = ($self->normalising)
132             ? $self->normalise( $duration )
133             : $duration->deltas;
134              
135 31         175 return $self->format_duration_from_deltas(
136             pattern => [@formats],
137             %duration
138             );
139             }
140              
141              
142             sub format_duration_from_deltas {
143 208     208 1 335 my $self = shift;
144              
145 208         827 my %args = validate( @_, {
146             pattern => { type => SCALAR | ARRAYREF, default => $self->pattern },
147             negative => { type => SCALAR, default => 0 },
148             years => { type => SCALAR, default => 0 },
149             months => { type => SCALAR, default => 0 },
150             days => { type => SCALAR, default => 0 },
151             hours => { type => SCALAR, default => 0 },
152             minutes => { type => SCALAR, default => 0 },
153             seconds => { type => SCALAR, default => 0 },
154             nanoseconds => { type => SCALAR, default => 0 },
155             });
156              
157 208 100       3691 my @formats = ref($args{pattern}) ? @{$args{pattern}} : ($args{pattern});
  31         72  
158 208         448 delete $args{pattern};
159 208 100       1044 my %duration = ($self->normalising)
160             ? $self->normalise( %args )
161             : %args;
162              
163 208         984 my @r;
164 208         1062 foreach my $f (@formats)
165             {
166             # regex from Date::Format - thanks Graham!
167 208         1576 $f =~ s/
168             %(\d*)([%a-zA-MO-Z]) # N returns from the left rather than the right
169             /
170 610 50       3824 $formats{$2}
    50          
171             ? ($1)
172             ? sprintf("%0$1d", substr($formats{$2}->(\%duration, $self),$1*-1) )
173             : $formats{$2}->(\%duration, $self)
174             : $1
175              
176             /sgex;
177              
178             # %3N
179 208         922 $f =~ s/
180             %(\d*)N
181             /
182 27         78 $formats{N}->(\%duration, $1)
183             /sgex;
184              
185 208 50       2600 return $f unless wantarray;
186              
187 0         0 push @r, $f;
188             }
189              
190 0         0 return @r;
191             }
192              
193              
194             sub parse_duration {
195 24     24 1 32 my $self = shift;
196 24         54 DateTime::Duration->new(
197             $self->parse_duration_as_deltas(@_)
198             );
199             }
200              
201             sub parse_duration_as_deltas {
202 196     196 1 133631 my ( $self, $time_string ) = @_;
203              
204 196         953 local $^W = undef;
205              
206             # Variables from the parser
207 196         448 my ( $centuries, $years, $months,
208             $weeks, $days, $hours,
209             $minutes, $seconds, $nanoseconds
210             );
211              
212             # Variables for DateTime
213 196         434 my ( $Years, $Months, $Days,
214             $Hours, $Minutes, $Seconds, $Nanoseconds,
215             ) = ();
216              
217             # Run the parser
218 196   66     1027 my $parser = $self->{parser} || $self->_build_parser;
219 196         35966 eval($parser);
220 196 50       1167 die "Parser ($parser) died:$@" if $@;
221              
222 196         546 $years += ($centuries * 100);
223 196         353 $days += ($weeks * 7 );
224              
225             return (
226 196   100     4333 years => $years || 0,
      100        
      100        
      100        
      100        
      100        
      100        
227             months => $months || 0,
228             days => $days || 0,
229             hours => $hours || 0,
230             minutes => $minutes || 0,
231             seconds => $seconds || 0,
232             nanoseconds => $nanoseconds || 0,
233             );
234              
235             }
236              
237              
238             #---------------------------------------------------------------------------
239             # UTILITY FUNCTIONS
240             #---------------------------------------------------------------------------
241              
242             sub normalise {
243 197     197 1 294 my $self = shift;
244              
245 197 100 33     1034 return $self->normalise_no_base(@_)
      66        
246             if (
247             ($self->{normalising} and $self->{normalising} =~ /^ISO$/i)
248             or not $self->base
249             );
250              
251 69 100       9384 my %delta = (ref($_[0]) =~/^DateTime::Duration/)
252             ? $_[0]->deltas
253             : @_;
254              
255 69 50       311 if (delete $delta{negative}) {
256 0         0 foreach (keys %delta) { $delta{$_} *= -1 }
  0         0  
257             }
258              
259              
260              
261 69 50       293 if ($self->{diagnostic}) {require Data::Dumper; print 'Pre Normalise: ' . Data::Dumper::Dumper( \%delta );}
  0         0  
  0         0  
262              
263 69         159 my $start = $self->base->clone;
264 69         3942 my $end = $self->base->clone;
265             # Can't just add the hash as ->add(%delta) because of mixed positivity:
266 69         4071 foreach (qw/years months days hours minutes seconds nanoseconds/) {
267 483   100     2893 $end->add( $_ => $delta{$_}||0 );
268 483 50       143202 print "Adding $delta{$_} $_: " . $end->datetime . "\n" if $self->{diagnostic};
269             }
270              
271              
272 69         127 my %new_delta;
273 69         116 my $set_negative = 0;
274 69 100       225 if ($start > $end){
275 14         1128 ($start, $end) = ($end, $start);
276 14         26 $set_negative = 1;
277             }
278              
279             # Creeping method:
280 69         4192 $new_delta{years} = $end->year - $start->year;
281 69 50       678 printf("Adding %d years: %s\n", $new_delta{years}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
282              
283 69         255 $new_delta{months} = $end->month - $start->month;
284 69 50       666 printf("Adding %d months: %s\n", $new_delta{months}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
285              
286 69         233 $new_delta{days} = $end->day - $start->day;
287 69 50       644 printf("Adding %d days: %s\n", $new_delta{days}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
288              
289 69         567 $new_delta{hours} = $end->hour - $start->hour;
290 69 50       647 printf("Adding %d hours: %s\n", $new_delta{hours}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
291              
292 69         338 $new_delta{minutes} = $end->minute - $start->minute;
293 69 50       629 printf("Adding %d minutes: %s\n", $new_delta{minutes}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
294              
295 69         195 $new_delta{seconds} = $end->second - $start->second;
296 69 50       655 printf("Adding %d seconds: %s\n", $new_delta{seconds}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
297              
298 69         206 $new_delta{nanoseconds} = $end->nanosecond - $start->nanosecond;
299 69 50       643 printf("Adding %d nanoseconds: %s\n", $new_delta{nanoseconds}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
300              
301              
302              
303              
304 69 50       218 if( $new_delta{nanoseconds} < 0 ){
305 0         0 $new_delta{nanoseconds} += MAX_NANOSECONDS;
306 0         0 $new_delta{seconds}--;
307 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};
308             }
309              
310 69 100       303 if( $new_delta{seconds} < 0 ){
311 4         19 $new_delta{seconds} += $end->clone->truncate( to => 'minute' )->subtract( seconds => 1 )->second + 1;
312 4         5123 $new_delta{minutes}--;
313 4 50       16 printf("Oops: Adding %d seconds, %d minutes: %s\n", $new_delta{seconds}, $new_delta{minutes}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
314             }
315              
316 69 100       212 if( $new_delta{minutes} < 0 ){
317 10         20 $new_delta{minutes} += 60;
318 10         19 $new_delta{hours}--;
319 10 50       35 printf("Oops: Adding %d minutes, %d hours: %s\n", $new_delta{minutes}, $new_delta{hours}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
320             }
321              
322 69 100       168 if( $new_delta{hours} < 0 ){
323 11         51 $new_delta{hours} += _hours_in_day($end->clone->truncate( to => 'day' )->subtract( seconds => 5 ));
324 11         22582 $new_delta{days}--;
325 11 50       55 printf("Oops: Adding %d hours, %d days: %s\n", $new_delta{hours}, $new_delta{days}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
326             }
327              
328 69 100       187 if( $new_delta{days} < 0 ){
329             # Thought this was correct .. I was wrong, but I want to leave it here anyway
330             # $new_delta{days} += $end->clone->truncate( to => 'month' )->subtract( seconds => 5 )->day;
331 16         170 $new_delta{days} += $start->clone->truncate( to => 'month' )->add(months => 1)->subtract( seconds => 5 )->day;
332 16         45912 $new_delta{months}--;
333 16 50       86 printf("Oops: Adding %d days, %d months: %s\n", $new_delta{days}, $new_delta{months}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
334             }
335              
336 69 100       190 if( $new_delta{months} < 0 ){
337 14         30 $new_delta{months} += 12;
338 14         28 $new_delta{years}--;
339 14 50       55 printf("Oops: Adding %d months, %d years: %s\n", $new_delta{months}, $new_delta{years}, $start->clone->add( %new_delta )->datetime) if $self->{diagnostic};
340             }
341              
342              
343 69         129 $new_delta{negative} = $set_negative;
344              
345 69 50       179 if ($self->{diagnostic}) {require Data::Dumper; print 'Post Normalisation: ' . Data::Dumper::Dumper( \%new_delta );}
  0         0  
  0         0  
346              
347 69         913 return %new_delta
348             }
349             *normalize = \&normalise;
350             *normalize = \&normalise;
351              
352             sub normalise_no_base {
353 128     128 0 176 my $self = shift;
354 128 100       1167 my %delta = (ref($_[0]) =~/^DateTime::Duration/) ? $_[0]->deltas : @_;
355              
356 128 100       697 if (delete $delta{negative}) {
357 1         4 foreach (keys %delta) { $delta{$_} *= -1 }
  7         8  
358             }
359 128         261 foreach(qw/years months days hours minutes seconds nanoseconds/) {
360 896   100     3764 $delta{$_} ||= 0;
361             }
362              
363 128 50       484 if ($self->{diagnostic}) {
364 0         0 require Data::Dumper;
365 0         0 print 'Pre Baseless Normalise: ' . Data::Dumper::Dumper( \%delta );
366             }
367              
368             # Remove any decimals:
369 128         1130 $delta{nanoseconds} += (MAX_NANOSECONDS * ($delta{seconds} - int($delta{seconds})));
370 128         261 $delta{seconds} = int($delta{seconds});
371 128         234 $delta{seconds} += (60 * ($delta{minutes} - int($delta{minutes})));
372 128         251 $delta{minutes} = int($delta{minutes});
373 128         219 $delta{minutes} += (60 * ($delta{hours} - int($delta{hours})));
374 128         178 $delta{hours} = int($delta{hours});
375 128         209 $delta{hours} += (24 * ($delta{days} - int($delta{days})));
376 128         161 $delta{days} = int($delta{days});
377 128         236 $delta{days} += (30 * ($delta{months} - int($delta{months})));
378 128         1383 $delta{months} = int($delta{months});
379              
380 128         414 ($delta{nanoseconds}, $delta{seconds}) = _set_max($delta{nanoseconds}, MAX_NANOSECONDS, $delta{seconds});
381 128         688 ($delta{seconds}, $delta{minutes}) = _set_max($delta{seconds}, 60, $delta{minutes});
382 128         476 ($delta{minutes}, $delta{hours}) = _set_max($delta{minutes}, 60, $delta{hours} );
383 128         275 ($delta{hours}, $delta{days}) = _set_max($delta{hours}, 24, $delta{days} );
384 128 100       481 ($delta{days}, $delta{months}) = _set_max($delta{days}, 30, $delta{months} )
385             if $self->{normalise} =~ /^iso$/i;
386 128         324 ($delta{months}, $delta{years}) = _set_max($delta{months}, 12, $delta{years} );
387              
388 128 50       501 if ($self->{diagnostic}) {
389 0         0 require Data::Dumper;
390 0         0 print 'Post Baseless Normalise: ' . Data::Dumper::Dumper( \%delta );
391             }
392              
393 128         1847 %delta = _denegate( %delta );
394              
395 128 50       708 if ($self->{diagnostic}) {
396 0         0 require Data::Dumper;
397 0         0 print 'Post Denegation: ' . Data::Dumper::Dumper( \%delta );
398             }
399              
400 128         984 return %delta;
401             }
402             *normalize_no_base = \&normalise_no_base;
403             *normalize_no_base = \&normalise_no_base;
404              
405             sub as_weeks {
406 2     2 0 4 my $self = shift;
407 2         7 return int($self->as_seconds($_[0]) / (7*24*60*60));
408             }
409              
410             sub as_days {
411 3     3 0 5 my $self = shift;
412 3         11 return int($self->as_seconds($_[0]) / (24*60*60));
413             }
414              
415             sub as_seconds {
416 7     7 0 9 my $self = shift;
417              
418 7 50       21 my %delta = (ref($_[0])) ? %{$_[0]} : @_;
  7         38  
419 7 50       27 if (delete $delta{negative}) {foreach( keys %delta ) { $delta{$_} *= -1 }};
  0         0  
  0         0  
420              
421 7 50       19 unless ($self->base) {
422 0         0 my $seconds = $delta{nanoseconds} / MAX_NANOSECONDS;
423 0         0 $seconds += $delta{seconds};
424 0         0 $seconds += $delta{minutes} * 60;
425 0         0 $seconds += $delta{hours} * (60*60);
426 0         0 $seconds += $delta{days} * (24*60*60);
427 0         0 $seconds += $delta{months} * (30*24*60*60);
428 0         0 $seconds += $delta{years} * (12*30*24*60*60);
429 0         0 return $seconds;
430             }
431              
432 7         513 my $dt1 = $self->base + DateTime::Duration->new( %delta );
433 7         4630 return int(($dt1->{utc_rd_days} - $self->base->{utc_rd_days}) * (24*60*60))
434             + ($dt1->{utc_rd_secs} - $self->base->{utc_rd_secs});
435             }
436              
437              
438             sub debug_level{
439 48     48 0 62 my $self = shift;
440 48         54 my $level = shift;
441 48 50       89 if ($level > 0) {
442 0         0 Params::Validate::validation_options(
443             on_fail => \&Carp::confess,
444             );
445             } else {
446 48         610 Params::Validate::validation_options(
447             on_fail => undef,
448             );
449             }
450 48 50       1148 $self->{diagnostic} = ($level) ? $level-1 : 0;
451             }
452              
453              
454              
455             #---------------------------------------------------------------------------
456             # EXPORTABLE FUNCTIONS
457             #---------------------------------------------------------------------------
458              
459             sub strfduration { #format
460 24     24 0 4219 my %args = validate( @_, {
461             pattern => { type => SCALAR | ARRAYREF },
462             duration => { type => OBJECT },
463             normalise => { type => SCALAR, optional => 1 },
464             base => { type => OBJECT, optional => 1 },
465             debug => { type => SCALAR, default => 0 },
466             });
467 24         897 my $new = DateTime::Format::Duration->new(
468             pattern => $args{pattern},
469             base => $args{base},
470             normalise=> $args{normalise},
471             );
472 24         75 $new->debug_level( $args{debug } );
473 24         71 return $new->format_duration( $args{duration} );
474             }
475              
476             sub strpduration { #parse
477 24     24 0 1867 my %args = validate( @_, {
478             pattern => { type => SCALAR | ARRAYREF },
479             duration => { type => SCALAR },
480             base => { type => OBJECT, optional => 1 },
481             as_deltas => { type => SCALAR, default => 0 },
482             debug => { type => SCALAR, default => 0 },
483             });
484 24         303 my $new = DateTime::Format::Duration->new(
485             pattern => $args{pattern},
486             base => $args{base},
487             );
488 24         76 $new->debug_level( $args{debug} );
489 24 50       365 return $new->parse_duration( $args{duration} ) unless $args{as_deltas};
490              
491 0         0 return $new->parse_duration_as_deltas( $args{duration} );
492             }
493              
494              
495              
496             #---------------------------------------------------------------------------
497             # INTERNAL FUNCTIONS
498             #---------------------------------------------------------------------------
499              
500             sub _format_nanosecs {
501 27     27   26 my %deltas = %{+shift};
  27         128  
502 27         66 my $precision = shift;
503              
504 27         91 my $ret = sprintf( "%09d", $deltas{nanoseconds} );
505 27 100       128 return $ret unless $precision; # default = 9 digits
506              
507 2         8 my ( $int, $frac ) = split(/[.,]/, $deltas{nanoseconds});
508 2 50       6 $ret .= $frac if $frac;
509              
510 2         12 return substr( $ret, 0, $precision );
511             }
512              
513             sub _build_parser {
514 28     28   40 my $self = shift;
515 28   33     104 my $regex = my $field_list = shift || $self->pattern;
516 28         221 my @fields = $field_list =~ m/(%\{\w+\}|%\d*.)/g;
517 28         66 $field_list = join('',@fields);
518              
519 28         137 my $tempdur = DateTime::Duration->new( seconds => 0 ); # Created just so we can do $tempdt->can(..)
520              
521             # I'm absoutely certain there's a better way to do this:
522 28         3603 $regex=~s|([\/\.\-])|\\$1|g;
523              
524 28         112 $regex =~ s/%[Tr]/%H:%M:%S/g;
525 28         67 $field_list =~ s/%[Tr]/%H%M%S/g;
526             # %T is the time as %H:%M:%S.
527              
528 28         42 $regex =~ s/%R/%H:%M/g;
529 28         190 $field_list =~ s/%R/%H%M/g;
530             #is the time as %H:%M.
531              
532 28         54 $regex =~ s|%F|%Y\\-%m\\-%d|g;
533 28         45 $field_list =~ s|%F|%Y%m%d|g;
534             #is the same as %Y-%m-%d
535              
536             # Negative and Positive
537 28         50 $regex =~ s/%P/[+-]?/g;
538 28         42 $field_list =~ s/%P//g;#negative#/g;
539              
540              
541             # Numerated places:
542              
543             # Centuries:
544 28 50       56 $regex =~ s/%(\d*)[C]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  1         9  
545 28         46 $field_list =~ s/%(\d*)[C]/#centuries#/g;
546              
547             # Years:
548 28 50       89 $regex =~ s/%(\d*)[Yy]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  7         144  
549 28         130 $field_list =~ s/%(\d*)[Yy]/#years#/g;
550              
551             # Months:
552 28 50       59 $regex =~ s/%(\d*)[m]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  6         32  
553 28         56 $field_list =~ s/%(\d*)[m]/#months#/g;
554              
555             # Weeks:
556 28 50       60 $regex =~ s/%(\d*)[GV]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  1         6  
557 28         64 $field_list =~ s/%(\d*)[GV]/#weeks#/g;
558 28         185 $regex =~ s/%\d*[W]/" *([+-]?\\d+\\.?\\d*)"/eg;
  1         3  
559 28         44 $field_list =~ s/%\d*[W]/#weeks#/g;
560              
561             # Days:
562 28 50       110 $regex =~ s/%(\d*)[deju]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  9         57  
563 28         73 $field_list =~ s/%(\d*)[deju]/#days#/g;
564              
565             # Hours:
566 28 50       75 $regex =~ s/%(\d*)[HIkl]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  11         58  
567 28         72 $field_list =~ s/%(\d*)[HIkl]/#hours#/g;
568              
569             # Minutes:
570 28 50       560 $regex =~ s/%(\d*)[M]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  8         39  
571 28         64 $field_list =~ s/%(\d*)[M]/#minutes#/g;
572              
573             # Seconds:
574 28 50       130 $regex =~ s/%(\d*)[sS]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  8         35  
575 28         57 $field_list =~ s/%(\d*)[sS]/#seconds#/g;
576              
577             # Nanoseconds:
578 28 50       42 $regex =~ s/%(\d*)[N]/($1) ? " *([+-]?\\d{$1})" : " *([+-]?\\d+)"/eg;
  1         6  
579 28         45 $field_list =~ s/%(\d*)[N]/#nanoseconds#/g;
580              
581              
582             # Any function in DateTime.
583 28 0       43 $regex =~ s|%{(\w+)}|($tempdur->can($1)) ? "(.+)" : ".+"|eg;
  0         0  
584 28 0       45 $field_list =~ s|(%{(\w+)})|($tempdur->can($2)) ? "#$2#" : $1 |eg;
  0         0  
585              
586             # White space:
587 28 50       45 $regex =~ s/%(\d*)[tn]/($1) ? "\\s{$1}" : "\\s+"/eg;
  2         10  
588 28         54 $field_list =~ s/%(\d*)[tn]//g;
589              
590             # is replaced by %.
591 28         39 $regex =~ s/%%/%/g;
592 28         36 $field_list =~ s/%%//g;
593              
594 28         209 $field_list=~s/#([a-z0-9_]+)#/\$$1, /gi;
595 28         272 $field_list=~s/,\s*$//;
596              
597 28 50       304 croak("Unknown symbols in parse: $&") if $field_list=~/(\%\w)/g;
598              
599 28         258 $self->{parser} = qq|($field_list) = \$time_string =~ /$regex/|;
600             }
601              
602             sub _set_max {
603             #$$_[0] should roll over to the next $$_[2] when it reaches $_[1]
604             #seconds should roll over to the next minute when it reaches 60.
605 680     680   1168 my ($small, $max, $large) = @_;
606             #warn "$small should roll over to the next $large when it reaches $max\n";
607 680         1811 $large += int($small / $max);
608 680 100       1383 $small = ($small < 0)
609             ? $small % -$max
610             : $small % $max;
611 680         2409 return ($small, $large);
612             }
613              
614             sub _denegate {
615 130     130   504 my %delta = @_;
616 130         157 my ($negatives, $positives);
617 130         425 foreach(qw/years months days hours minutes seconds nanoseconds/) {
618 910 100       3712 if ($delta{$_} < 0) {
    100          
619 35         144 $negatives++;
620             } elsif ($delta{$_} > 0) {
621 139         201 $positives++;
622             } # ignore == 0
623             }
624 130 100 100     896 if ($negatives and not $positives) {
    100 66        
625 22         55 foreach(qw/years months days hours minutes seconds nanoseconds/) {
626 154 100       527 if ($delta{$_} < 0) {
627 31         49 $delta{$_} *= -1
628             }
629 154   100     790 $delta{$_} ||= 0;
630             }
631 22         59 $delta{negative} = 1;
632             } elsif ($negatives and $positives) {
633             # Work to match largest component
634 2         5 my $make = '';
635 2         6 foreach(qw/years months days hours minutes seconds nanoseconds/) {
636 8 100       41 if ($delta{$_} < 0) {
    50          
637 2         5 $make = 'negative';
638 2         6 last;
639             } elsif ($delta{$_} > 0) {
640 0         0 $make = 'positive';
641 0         0 last;
642             }
643             }
644 2 50       9 if ($make) {
645 2         12 ($delta{seconds}, $delta{minutes}) = _make($make,$delta{seconds}, 60, $delta{minutes});
646 2         11 ($delta{minutes}, $delta{hours}) = _make($make,$delta{minutes}, 60, $delta{hours} );
647 2         8 ($delta{hours}, $delta{days}) = _make($make,$delta{hours}, 24, $delta{days} );
648 2         8 ($delta{months}, $delta{years}) = _make($make,$delta{months}, 12, $delta{years} );
649 2         48 %delta = _denegate(%delta);
650             }
651             }
652 130         2079 return %delta
653             }
654              
655             sub _make {
656 8     8   18 my ($make, $small, $max, $large) = @_;
657 8   66     35 while ($small < 0 and $make eq 'positive') {
658 0         0 $small += $max;
659 0         0 $large -= 1;
660             }
661 8   66     35 while ($small > 0 and $make eq 'negative') {
662 2         4 $small -= $max;
663 2         8 $large += 1;
664             }
665 8         26 return ($small, $large);
666             }
667              
668             sub _hours_in_day{
669 11     11   13610 my $day = shift;
670              
671             return (
672 11         48 $day->clone->truncate( to => 'day' )->add( days => 1 )->epoch
673             -
674             $day->clone->truncate( to => 'day' )->epoch
675             ) / (60 * 60)
676              
677             }
678              
679             1;
680              
681             __END__
682              
683             =head1 NAME
684              
685             DateTime::Format::Duration - Format and parse DateTime::Durations
686              
687             =head1 SYNOPSIS
688              
689             use DateTime::Format::Duration;
690              
691             $d = DateTime::Format::Duration->new(
692             pattern => '%Y years, %m months, %e days, '.
693             '%H hours, %M minutes, %S seconds'
694             );
695              
696             print $d->format_duration(
697             DateTime::Duration->new(
698             years => 3,
699             months => 5,
700             days => 1,
701             hours => 6,
702             minutes => 15,
703             seconds => 45,
704             nanoseconds => 12000
705             )
706             );
707             # 3 years, 5 months, 1 days, 6 hours, 15 minutes, 45 seconds
708              
709              
710             $duration = $d->parse_duration(
711             '3 years, 5 months, 1 days, 6 hours, 15 minutes, 45 seconds'
712             );
713             # Returns DateTime::Duration object
714              
715              
716             print $d->format_duration_from_deltas(
717             years => 3,
718             months => 5,
719             days => 1,
720             hours => 6,
721             minutes => 15,
722             seconds => 45,
723             nanoseconds => 12000
724             );
725             # 3 years, 5 months, 1 days, 6 hours, 15 minutes, 45 seconds
726              
727             %deltas = $d->parse_duration_as_deltas(
728             '3 years, 5 months, 1 days, 6 hours, 15 minutes, 45 seconds'
729             );
730             # Returns hash:
731             # (years=>3, months=>5, days=>1, hours=>6, minutes=>15, seconds=>45)
732              
733             =head1 ABSTRACT
734              
735             This module formats and parses L<DateTime::Duration> objects
736             as well as other durations representations.
737              
738             =head1 CONSTRUCTOR
739              
740             This module contains a single constructor:
741              
742             =over 4
743              
744             =item * C<new( ... )>
745              
746             The C<new> constructor takes the following attributes:
747              
748             =over 4
749              
750             =item * C<pattern => $string>
751              
752             This is a strf type pattern detailing the format of the duration.
753             See the L</Patterns> sections below for more information.
754              
755             =item * C<<normalise => $one_or_zero_or_ISO>>
756              
757             =item * C<<normalize => $one_or_zero_or_ISO>>
758              
759             This determines whether durations are 'normalised'. For example, does
760             120 seconds become 2 minutes?
761              
762             Setting this value to true without also setting a C<base> means we will
763             normalise without a base. See the L</Normalising without a base> section
764             below.
765              
766             =item * C<<base => $datetime_object>>
767              
768             If a base DateTime is given then that is the normalisation date. Setting
769             this attribute overrides the above option and sets normalise to true.
770              
771             =back
772              
773             =back
774              
775             =head1 METHODS
776              
777             L<DateTime::Format::Duration> has the following methods:
778              
779             =over 4
780              
781             =item * C<format_duration( $datetime_duration_object )>
782              
783             =item * C<<format_duration( duration => $dt_duration, pattern => $pattern )>>
784              
785             Returns a string representing a L<DateTime::Duration> object in the format set
786             by the pattern. If the first form is used, the pattern is taken from the
787             object. If the object has no pattern then this method will croak.
788              
789             =item * C<format_duration_from_deltas( %deltas )>
790              
791             =item * C<format_duration_from_deltas( %deltas, pattern => $pattern )>
792              
793             As above, this method returns a string representing a duration in the format
794             set by the pattern. However this method takes a hash of values. Permissable
795             hash keys are C<years, months, days, hours, minutes, seconds> and C<nanoseconds>
796             as well as C<negative> which, if true, inverses the duration. (C<< years => -1 >> is
797             the same as C<< years => 1, negative=>1 >>)
798              
799             =item * C<parse_duration( $string )>
800              
801             This method takes a string and returns a L<DateTime::Duration> object that is the
802             equivalent according to the pattern.
803              
804             =item * C<parse_duration_as_deltas( $string )>
805              
806             Once again, this method is the same as above, however it returns a hash rather
807             than an object.
808              
809             =item * C<normalise( $duration_object )>
810              
811             =item * C<normalize( %deltas )>
812              
813             Returns a hash of deltas after normalising the input. See the L</NORMALISE>
814             section below for more information.
815              
816             =back
817              
818             =head1 ACCESSORS
819              
820             =over 4
821              
822             =item * C<pattern()>
823              
824             Returns the current pattern.
825              
826             =item * C<base()>
827              
828             Returns the current base.
829              
830             =item * C<normalising()>
831              
832             Indicates whether or not the durations are being normalised.
833              
834             =back
835              
836             =head1 SETTERS
837              
838             All setters return the object so that they can be strung together.
839              
840             =over 4
841              
842             =item * C<set_pattern( $new_pattern )>
843              
844             Sets the pattern and returns the object.
845              
846             =item * C<set_base( $new_DateTime )>
847              
848             Sets the base L<DateTime> and returns the object.
849              
850             =item * C<set_normalising( $true_or_false_or_ISO )>
851              
852             Turns normalising on or off and returns the object.
853              
854             =back
855              
856             =head1 NOTES
857              
858             =head2 Patterns
859              
860             This module uses a similar set of patterns to L<strftime|strftime(3)>. These patterns
861             have been kept as close as possible to the original time-based patterns.
862              
863             =over 4
864              
865             =item * %C
866              
867             The number of hundreds of years in the duration. 400 years would return 4.
868             This is similar to centuries.
869              
870             =item * %d
871              
872             The number of days zero-padded to two digits. 2 days returns 02. 22 days
873             returns 22 and 220 days returns 220.
874              
875             =item * %e
876              
877             The number of days.
878              
879             =item * %F
880              
881             Equivelent of %Y-%m-%d
882              
883             =item * %H
884              
885             The number of hours zero-padded to two digits.
886              
887             =item * %I
888              
889             Same as %H
890              
891             =item * %j
892              
893             The duration expressed in whole days. 36 hours returns 1
894              
895             =item * %k
896              
897             The hours without any padding
898              
899             =item * %l
900              
901             Same as %k
902              
903             =item * %m
904              
905             The months, zero-padded to two digits
906              
907             =item * %M
908              
909             The minutes, zero-padded to two digits
910              
911             =item * %n
912              
913             A linebreak when formatting and any whitespace when parsing
914              
915             =item * %N
916              
917             Nanoseconds - see note on precision at end
918              
919             =item * %p
920              
921             Either a '+' or a '-' indicating the positive-ness of the duration
922              
923             =item * %P
924              
925             A '-' for negative durations and nothing for positive durations.
926              
927             =item * %r
928              
929             Equivelent of %H:%M:%S
930              
931             =item * %R
932              
933             Equivelent of %H:%M
934              
935             =item * %s
936              
937             Returns the value as seconds. 1 day, 5 seconds return 86405
938              
939             =item * %S
940              
941             Returns the seconds, zero-padded to two digits
942              
943             =item * %t
944              
945             A tab character when formatting or any whitespace when parsing
946              
947             =item * %T
948              
949             Equivelent of %P%H:%M:%S
950              
951             =item * %u
952              
953             Days after weeks are removed. 4 days returns 4, but 22 days returns 1
954             (22 days is three weeks, 1 day)
955              
956             =item * %V
957              
958             Duration expressed as weeks. 355 days returns 52.
959              
960             =item * %W
961              
962             Duration expressed as floating weeks. 10 days, 12 hours returns 1.5 weeks.
963              
964             =item * %y
965              
966             Years in the century. 145 years returns 45.
967              
968             =item * %Y
969              
970             Years, zero-padded to four digits
971              
972             =item * %%
973              
974             A '%' symbol
975              
976             =back
977              
978             B<Precision> can be changed for any and all the above values. For all but
979             nanoseconds (%N), the precision is the zero-padding. To change the precision
980             insert a number between the '%' and the letter. For example: 1 year formatted
981             with %6Y would return 000001 rather than the default 0001. Likewise, to remove
982             padding %1Y would just return a 1.
983              
984             Nanosecond precision is the other way (nanoseconds are fractional and thus
985             should be right padded). 123456789 nanoseconds formatted with %3N would return
986             123 and formatted as %12N would return 123456789000.
987              
988             =head2 Normalisation
989              
990             This module contains a complex method for normalising durations. The method
991             ensures that the vslues for all components are as close to zero as possible.
992             Rather than returning 68 minutes, it is normalised to 1 hour, 8 minutes.
993              
994             The complexity comes from three places:
995              
996             =over 4
997              
998             =item * Mixed positive and negative components
999              
1000             The duration of 1 day, minus 2 hours is easy to normalise in your head to
1001             22 hours. However consider something more complex such as -2 years, +1 month,
1002             +22 days, +11 hours, -9 minutes.
1003              
1004             This module works from lowest to highest precision to calculate the duration.
1005             So, based on a C<base> of 2004-03-28T00:00:00 the following transformations take
1006             place:
1007              
1008             2003-01-01T00:00:00 - 2 years = 2001-01-01T00:00:00 === -2 years
1009             2001-01-01T00:00:00 + 1 month = 2001-02-01T00:00:00 === -1 year, 11 months
1010             2001-02-01T00:00:00 + 22 days = 2001-02-23T00:00:00 === -1yr, 10mths, 6days
1011             2001-02-22T00:00:00 + 11 hours = 2001-02-23T11:00:00 === -1y, 10m, 6d, 13h
1012             2001-02-22T11:00:00 - 9 minutes = 2001-02-23T10:51:00 === -1y, 10m, 6d, 13h, 9m
1013              
1014             =for html <img src="http://search.cpan.org/src/RICKM/DateTime-Format-Duration-1.0002/docs/figure1.gif">
1015              
1016             =for man See: http://search.cpan.org/src/RICKM/DateTime-Format-Duration-1.0002/docs/figure1.gif
1017              
1018             Figure 1 illustrates that, with the given base, -2 years, +1 month,
1019             +22 days, +11 hours, -9 minutes is normalised to -1 year, 10 months, 6 days,
1020             13 hours and 9 minutes.
1021              
1022             =item * Months of unequal length.
1023              
1024             Unfortunately months can have 28, 29, 30 or 31 days and it can change from year
1025             to year. Thus if I wanted to normalise 2 months it could be any of 59 (Feb-Mar),
1026             60 (Feb-Mar in a leap year), 61 (Mar-Apr, Apr-May, May-Jun, Jun-Jul, Aug-Sep,
1027             Sep-Oct, Oct-Nov or Nov-Dec) or 62 days (Dec-Jan or Jul-Aug). Because of this
1028             the module uses a base datetime for its calculations. If we use the base
1029             2003-01-01T00:00:00 then two months would be 59 days (2003-03-01 - 2003-01-01)
1030              
1031             =item * The order of components
1032              
1033             Components will always be assessed from lowest to highest precision (years, months,
1034             days, hours, minutes, seconds, nanoseconds). This can really change things.
1035              
1036             Consider the duration of 1 day, 24 hours. Normally this will normalise to 2 days.
1037             However, consider changes to Daylight Savings. On the changes to and from DST
1038             days have 25 and 23 hours.
1039              
1040             If we take the base DateTime as midnight on the day DST ends (when there's 25
1041             hours in the day), and add 1 day, 24 hours we end up at midnight 2 days later.
1042             So our duration normalises to two days.
1043              
1044             However, if we add 24 hours, 1 day we end up at 11pm on the next day! Why is this?
1045             Because midnight + 24 hours = 11pm (there's 25 hours on this day!), then we add 1
1046             day and end up at 11pm on the following day.
1047              
1048             =for html <img src="http://search.cpan.org/src/RICKM/DateTime-Format-Duration-1.0002/docs/figure2.gif">
1049              
1050             =for man See: http://search.cpan.org/src/RICKM/DateTime-Format-Duration-1.0002/docs/figure2.gif
1051              
1052             Figure 2 illustrates the above problem on timelines.
1053              
1054              
1055             =item * Leap years and leap seconds
1056              
1057             Leap years and seconds further add to the confusion in normalisation. Leap
1058             seconds mean there are minutes that are 61 seconds long, thus 130 seconds can
1059             be 2 minutes, 10 seconds or 2 minutes 9 seconds, depending on the base DateTime.
1060             Simmilarly leap years mean a day can have 23, 24 or 25 hours.
1061              
1062             =for html <img src="http://search.cpan.org/src/RICKM/DateTime-Format-Duration-1.0002/docs/figure3.gif">
1063              
1064             =for man See: http://search.cpan.org/src/RICKM/DateTime-Format-Duration-1.0002/docs/figure3.gif
1065              
1066             Figure 3 shows how leaps are calculated on timelines.
1067              
1068             =back
1069              
1070             =head2 Normalising without a base
1071              
1072             This module includes two ways to normalise without a base.
1073              
1074             =over 4
1075              
1076             =item * Standard Normalisation
1077              
1078             Using standard normalisation without a base, 45 days will stay as 45 days as there
1079             is no way to accurately convert to months. However the following assumptions will
1080             be made: There are 24 hours in a day and there are 60 seconds in a minute.
1081              
1082             =item * ISO Normalisation
1083              
1084             In ISO8601v2000, Section 5.5.3.2 says that "The values used must not exceed the
1085             'carry-over points' of 12 months, 30 days, 24 hours, 60 minutes and 60 seconds".
1086             Thus if you set the normalise option of the constructor, or use set_normalising
1087             to 'ISO', months will be normalised to 30 days.
1088              
1089             =back
1090              
1091             =head2 Deltas vs Duration Objects
1092              
1093             This module can bypass duration objects and just work with delta hashes.
1094             This used to be of greatest value with earlier versions of DateTime::Duration
1095             when DateTime::Duration assumed a duration with one negative component was a
1096             negative duration (that is, -2 hours, 34 minutes was assumed to be -2 hours,
1097             -34 minutes).
1098              
1099             These extra methods have been left in here firstly for backwards-compatibility
1100             but also as an added 'syntactic sugar'. Consider these two equivelent
1101             expressions:
1102              
1103             $one = $o->format_duration(
1104             DateTime::Duration->new(
1105             years => -2,
1106             days => 13,
1107             hours => -1
1108             )
1109             );
1110              
1111             $two = $o->format_duration_from_deltas(
1112             years => -2,
1113             days => 13,
1114             hours => -1
1115             );
1116              
1117             These both create the same string in $one and $two, but if you don't already
1118             have a DateTime::Duration object, the later looks cleaner.
1119              
1120             =head1 AUTHOR
1121              
1122             Rick Measham <rickm@cpan.org>
1123              
1124             =head1 COPYRIGHT
1125              
1126             Copyright (c) 2003 - 2004 Rick Measham. All rights reserved. This program
1127             is free software; you can redistribute it and/or modify it under the same
1128             terms as Perl itself.
1129              
1130             The full text of the license can be found in the LICENSE file included
1131             with this module.
1132              
1133             =head1 SEE ALSO
1134              
1135             datetime@perl.org mailing list
1136              
1137             http://datetime.perl.org/
1138              
1139             =cut
1140