File Coverage

lib/Date/Manip/Delta.pm
Criterion Covered Total %
statement 490 554 88.4
branch 218 272 80.1
condition 58 78 74.3
subroutine 31 32 96.8
pod 11 11 100.0
total 808 947 85.3


line stmt bran cond sub pod time code
1             package Date::Manip::Delta;
2             # Copyright (c) 1995-2026 Sullivan Beck. All rights reserved.
3             # This program is free software; you can redistribute it and/or modify it
4             # under the same terms as Perl itself.
5              
6             ########################################################################
7             # Any routine that starts with an underscore (_) is NOT intended for
8             # public use. They are for internal use in the the Date::Manip
9             # modules and are subject to change without warning or notice.
10             #
11             # ABSOLUTELY NO USER SUPPORT IS OFFERED FOR THESE ROUTINES!
12             ########################################################################
13              
14 170     170   1496 use Date::Manip::Obj;
  170         614  
  170         13230  
15             @ISA = ('Date::Manip::Obj');
16              
17             require 5.010000;
18 170     170   1143 use warnings;
  170         541  
  170         16775  
19 170     170   1163 use strict;
  170         571  
  170         6172  
20 170     170   986 use utf8;
  170         579  
  170         2443  
21 170     170   6388 use IO::File;
  170         485  
  170         43326  
22 170     170   1179 use Carp;
  170         426  
  170         14725  
23             #use re 'debug';
24              
25 170     170   1109 use Date::Manip::Base;
  170         428  
  170         5462  
26 170     170   812 use Date::Manip::TZ;
  170         391  
  170         288738  
27              
28             our $VERSION;
29             $VERSION='6.99';
30 170     170   1625 END { undef $VERSION; }
31              
32             ########################################################################
33             # BASE METHODS
34             ########################################################################
35              
36             sub is_delta {
37 1     1 1 169 return 1;
38             }
39              
40             sub config {
41 15     15 1 302 my($self,@args) = @_;
42 15         510 $self->SUPER::config(@args);
43              
44             # A new config can change the value of the format fields, so clear them.
45 15         56 $$self{'data'}{'f'} = {};
46 15         65 $$self{'data'}{'flen'} = {};
47             }
48              
49             # Call this every time a new delta is put in to make sure everything is
50             # correctly initialized.
51             #
52             sub _init {
53 12028     12028   16010 my($self) = @_;
54              
55 12028         20905 my $def = [0,0,0,0,0,0,0];
56 12028         15363 my $dmt = $$self{'tz'};
57 12028         13698 my $dmb = $$dmt{'base'};
58              
59 12028         14298 $$self{'err'} = '';
60 12028         82535 $$self{'data'} =
61             {
62             'delta' => $def, # the delta put in (all negative fields signed)
63              
64             'in' => '', # the string that was parsed (if any)
65             'length' => 0, # length of delta (in seconds)
66              
67             'gotmode' => 0, # 1 if mode set explicitly
68             'mode' => 'standard', # standard/business
69             'type' => 'exact', # exact, semi, estimated, approx
70             'type_from' => 'init', # where did the type come from
71             # init - from here
72             # opt - specified in an option/string
73             # det - determined automatically
74             'normalized' => 1, # 1 if normalized
75              
76             'f' => {}, # format fields
77             'flen' => {}, # field lengths
78             }
79             }
80              
81             sub _init_args {
82 2     2   8 my($self) = @_;
83              
84 2         13 my @args = @{ $$self{'args'} };
  2         22  
85 2         31 $self->parse(@args);
86             }
87              
88             sub value {
89 3422     3422 1 6285 my($self,$as_input) = @_;
90              
91 3422 50       5878 if ($$self{'err'}) {
92 0 0       0 return () if (wantarray);
93 0         0 return '';
94             }
95              
96 3422         3808 my $dmt = $$self{'tz'};
97 3422         4050 my $dmb = $$dmt{'base'};
98              
99 3422         3430 my @delta = @{ $$self{'data'}{'delta'} };
  3422         7898  
100              
101 3422 100       10467 return @delta if (wantarray);
102 339         366 my $err;
103              
104             my %o = ( 'source' => 'delta',
105             'nonorm' => 1,
106             'type' => $$self{'data'}{'type'},
107             'sign' => 0,
108 339         1215 'mode' => $$self{'data'}{'mode'},
109             );
110              
111 339         909 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
112 339         2418 return join(':',@delta);
113             }
114              
115             sub input {
116 0     0 1 0 my($self) = @_;
117 0         0 return $$self{'data'}{'in'};
118             }
119              
120             ########################################################################
121             # DELTA METHODS
122             ########################################################################
123              
124 0         0 BEGIN {
125 170     170   732144 my %f = qw( y 0 M 1 w 2 d 3 h 4 m 5 s 6 );
126              
127             sub set {
128 4988     4988 1 194552 my($self,@args) = @_;
129 4988         5759 my %opts;
130 4988 100       8164 if (ref($args[0]) eq 'HASH') {
131 120         134 %opts = %{ $args[0] };
  120         413  
132             } else {
133             # *** DEPRECATED 7.0 ***
134 4868 100       7328 if (@args == 3) {
135 15 50       65 %opts = ( $args[0] => $args[1],
136             'nonorm' => ($args[2] ? 1 : 0) );
137             } else {
138 4853         9427 %opts = ( $args[0] => $args[1] );
139             }
140             }
141              
142             # Check for some invalid opts
143              
144 4988         10946 foreach my $key (keys %opts) {
145 5163         6420 my $val = $opts{$key};
146 5163         6796 delete $opts{$key};
147              
148             # *** DEPRECATED 7.0 ***
149 5163 50       8961 $key = 'standard' if (lc($key) eq 'normal');
150              
151 5163 100 100     12200 if (lc($key) eq 'delta' ||
    100 100        
      100        
      100        
      100        
152             lc($key) eq 'business' ||
153             lc($key) eq 'standard' ||
154             lc($key) eq 'nonorm' ||
155             lc($key) eq 'mode' ||
156             lc($key) eq 'type') {
157              
158 5153 100       9164 if (exists $opts{lc($key)}) {
159 1         3 $key = lc($key);
160 1         7 $$self{'err'} = "[set] Invalid option: $key entered twice";
161 1         6 return 1;
162             }
163              
164 5152         9681 $opts{lc($key)} = $val;
165              
166             } elsif ($key =~ /^[yMwdhms]$/) {
167              
168 9         28 $opts{$key} = $val;
169              
170             } else {
171 1         3 $$self{'err'} = "[set] Unknown option: $key";
172 1         4 return 1;
173             }
174             }
175              
176 4986 100 66     43002 if ( (exists $opts{'delta'}) +
177             (exists $opts{'business'}) +
178             (exists $opts{'standard'}) +
179             (exists $opts{'y'} || exists $opts{'M'} || exists $opts{'w'} ||
180             exists $opts{'d'} || exists $opts{'h'} || exists $opts{'m'} ||
181             exists $opts{'s'})
182             > 1 ) {
183 2         4 $$self{'err'} = "[set] Fields set multiple times";
184 2         6 return 1;
185             }
186              
187 4984 100 100     8927 if (exists $opts{'mode'} && $opts{'mode'} !~ /^(business|standard)$/) {
188 1         7 $$self{'err'} = "[set] Unknown value for mode: $opts{mode}";
189 1         3 return 1;
190             }
191 4983 100 100     8923 if (exists $opts{'type'} &&
192             $opts{'type'} !~ /^(exact|semi|estimated|approx)$/) {
193 1         3 $$self{'err'} = "[set] Unknown value for type: $opts{type}";
194 1         2 return 1;
195             }
196              
197 4982 100       12488 if ( (exists $opts{'business'}) +
    100          
    100          
198             (exists $opts{'standard'}) +
199             (exists $opts{'mode'})
200             > 1 ) {
201 1         3 $$self{'err'} = "[set] Mode set multiple times";
202 1         4 return 1;
203             } elsif (exists $opts{'business'}) {
204 166         261 $opts{'delta'} = $opts{'business'};
205 166         243 $opts{'mode'} = 'business';
206             } elsif (exists $opts{'standard'}) {
207 8         14 $opts{'delta'} = $opts{'standard'};
208 8         14 $opts{'mode'} = 'standard';
209             }
210              
211             # If we are setting delta/business/standard, we need to initialize
212             # all the parameters.
213              
214 4981         5145 my @delta;
215 4981 100       6611 if (exists $opts{'delta'}) {
216 4938 100       9529 if (ref($opts{'delta'}) ne 'ARRAY') {
217 1         6 $$self{'err'} = "[set] Option delta requires an array value";
218 1         4 return 1;
219             }
220              
221             # Init everything because we're setting an entire new delta
222 4937         9346 $self->_init();
223 4937         6605 @delta = @{ $opts{'delta'} };
  4937         8762  
224              
225             } else {
226 43         43 @delta = @{ $$self{'data'}{'delta'} };
  43         105  
227             }
228              
229             # Figure out the parameters. Include the nonorm/mode/type
230             # options.
231              
232 4980         5754 my $err;
233 4980         5616 my $dmt = $$self{'tz'};
234 4980         5224 my $dmb = $$dmt{'base'};
235 4980 100       8326 my $gotmode = (exists $opts{'mode'} ? 1 : $$self{'data'}{'gotmode'});
236             my $mode = (exists $opts{'mode'} ? $opts{'mode'} :
237 4980 100       7814 $$self{'data'}{'mode'});
238 4980 100       6988 my $nonorm = (exists $opts{'nonorm'} ? $opts{'nonorm'} : 0);
239              
240 4980         5684 my ($type,$type_from);
241 4980 100       6318 if (exists $opts{'type'}) {
242 94         98 $type = $opts{'type'};
243 94         292 $type_from = 'opt';
244             } else {
245 4886         6198 $type = $$self{'data'}{'type'};
246 4886         6102 $type_from = $$self{'data'}{'type_from'};
247             }
248              
249             # If we're setting individual fields, do that now
250              
251             {
252 4980         4820 my $field_set = 0;
  4980         5088  
253              
254             # Check all individual fields
255 4980         6847 foreach my $opt (qw(y M w d h m s)) {
256 34848 100       45100 if (exists $opts{$opt}) {
257 8 100       27 if (ref($opts{$opt})) {
258 1         6 $$self{'err'} = "[set] Option $opt requires a scalar value";
259 1         9 return 1;
260             }
261 7         37 my $val = $opts{$opt};
262 7 100       26 if (! $dmb->_is_num($val)) {
263 1         5 $$self{'err'} = "[set] Option $opt requires a numerical value";
264 1         7 return 1;
265             }
266 6         27 $delta[ $f{$opt} ] = $val;
267 6         9 $field_set = 1;
268             }
269             }
270              
271             # If none were set, than we're done with setting.
272 4978 100       9085 last if (! $field_set);
273              
274 6 50       11 if ($$self{'err'}) {
275 0         0 return 1;
276             }
277             }
278              
279             # Check that the type is consistent with @delta.
280              
281 4978         14136 ($err,$type,$type_from) =
282             $dmb->_check_delta_type($mode,$type,$type_from,@delta);
283              
284 4978 100       9076 if ($err) {
285 11         17 $$self{'err'} = "[set] $err";
286 11         38 return 1;
287             }
288              
289 4967         16902 my %o = ( 'source' => 'delta',
290             'nonorm' => $nonorm,
291             'type' => $type,
292             'sign' => -1,
293             'mode' => $mode,
294             );
295              
296 4967         16345 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
297              
298 4967 100       10633 if ($err) {
299 2         4 $$self{'err'} = "[set] $err";
300 2         10 return 1;
301             }
302              
303 4965         12830 $$self{'data'}{'delta'} = [ @delta ];
304 4965         7207 $$self{'data'}{'mode'} = $mode;
305 4965         6282 $$self{'data'}{'gotmode'} = $gotmode;
306 4965         5750 $$self{'data'}{'type'} = $type;
307 4965         5724 $$self{'data'}{'type_from'} = $type_from;
308 4965         6909 $$self{'data'}{'normalized'} = 1-$nonorm;
309 4965         7089 $$self{'data'}{'length'} = 'unknown';
310 4965         6186 $$self{'data'}{'in'} = '';
311              
312 4965         17485 return 0;
313             }
314             }
315              
316             sub _rx {
317 2101     2101   2594 my($self,$rx) = @_;
318 2101         2176 my $dmt = $$self{'tz'};
319 2101         2119 my $dmb = $$dmt{'base'};
320              
321             return $$dmb{'data'}{'rx'}{'delta'}{$rx}
322 2101 100       4723 if (exists $$dmb{'data'}{'rx'}{'delta'}{$rx});
323              
324 84 100       282 if ($rx eq 'expanded') {
    100          
    50          
325 26         3679 my $sign = '[-+]?\s*';
326 26         52 my $sep = '(?:,\s*|\s+|$)';
327              
328 26         92 my $nth = $$dmb{'data'}{'rx'}{'nth'}[0];
329 26         73 my $yf = $$dmb{data}{rx}{fields}[1];
330 26         61 my $mf = $$dmb{data}{rx}{fields}[2];
331 26         73 my $wf = $$dmb{data}{rx}{fields}[3];
332 26         57 my $df = $$dmb{data}{rx}{fields}[4];
333 26         69 my $hf = $$dmb{data}{rx}{fields}[5];
334 26         64 my $mnf = $$dmb{data}{rx}{fields}[6];
335 26         64 my $sf = $$dmb{data}{rx}{fields}[7];
336 26         42 my $num = '(?:\d+(?:\.\d*)?|\.\d+)';
337              
338 26         126 my $y = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$yf)$sep)";
339 26         104 my $m = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$mf)$sep)";
340 26         75 my $w = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$wf)$sep)";
341 26         76 my $d = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$df)$sep)";
342 26         99 my $h = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$hf)$sep)";
343 26         87 my $mn = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$mnf)$sep)";
344 26         66 my $s = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$sf)?)";
345              
346 26         107770 my $exprx = qr/^\s*$y?$m?$w?$d?$h?$mn?$s?\s*$/i;
347 26         1945 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $exprx;
348              
349             } elsif ($rx eq 'mode') {
350              
351 32         984 my $mode = qr/\b($$dmb{'data'}{'rx'}{'mode'}[0])\b/i;
352 32         133 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $mode;
353              
354             } elsif ($rx eq 'when') {
355              
356 26         1533 my $when = qr/\b($$dmb{'data'}{'rx'}{'when'}[0])\b/i;
357 26         125 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $when;
358              
359             }
360              
361 84         264 return $$dmb{'data'}{'rx'}{'delta'}{$rx};
362             }
363              
364             sub parse {
365 773     773 1 257062 my($self,$instring,@args) = @_;
366 773         1678 $self->_init();
367              
368 773         1234 my %opts;
369 773 50       1493 if (ref($args[0]) eq 'HASH') {
370 0         0 %opts = %{ $args[0] };
  0         0  
371              
372             } else {
373             # *** DEPRECATED 7.0 ***
374              
375 773         915 my($business,$no_normalize);
376              
377 773 50       2166 if (@args == 2) {
    100          
    50          
378 0         0 ($business,$no_normalize) = (lc($args[0]),lc($args[1]));
379 0 0 0     0 if ($business eq 'standard' || ! $business) {
380 0         0 $opts{'mode'} = 'standard';
381             } else {
382 0         0 $opts{'mode'} = 'business';
383             }
384              
385 0 0       0 $opts{'nonorm'} = ($no_normalize ? 1 : 0);
386              
387             } elsif (@args == 1) {
388 1         9 my $arg = lc($args[0]);
389 1 50       16 if ($arg eq 'standard') {
    50          
    0          
    0          
390 0         0 $opts{'mode'} = 'standard';
391             } elsif ($arg eq 'business') {
392 1         8 $opts{'mode'} = 'business';
393             } elsif ($arg eq 'nonormalize') {
394 0         0 $opts{'nonorm'} = 1;
395             } elsif ($arg) {
396 0         0 $opts{'mode'} = 'business';
397             } else {
398 0         0 $opts{'mode'} = 'standard';
399             }
400              
401             } elsif (@args) {
402 0         0 $$self{'err'} = "[parse] Unknown arguments";
403 0         0 return 1;
404             }
405             }
406              
407 773         920 my $dmt = $$self{'tz'};
408 773         822 my $dmb = $$dmt{'base'};
409 773         1311 $self->_init();
410              
411 773 50       1378 if (! $instring) {
412 0         0 $$self{'err'} = '[parse] Empty delta string';
413 0         0 return 1;
414             }
415              
416             #
417             # Parse the string
418             # $err : any error
419             # @delta : the delta parsed
420             # $mode : the mode string (if any) in the string
421             #
422              
423 773         942 my ($err,@delta,$mode);
424 773         929 $mode = '';
425 773         923 $$self{'err'} = '';
426 773         2688 $instring =~ s/^\s*//;
427 773         3565 $instring =~ s/\s*$//;
428              
429             PARSE: {
430              
431             # First, we'll try the standard format (without a mode string)
432              
433 773         976 ($err,@delta) = $dmb->_split_delta($instring);
  773         2205  
434 773 100       1502 last PARSE if (! $err);
435              
436             # Next, we'll need to get a list of all the encodings and look
437             # for (and remove) the mode string from each. We'll also recheck
438             # the standard format for each.
439              
440 513         1270 my @strings = $dmb->_encoding($instring);
441 513         1157 my $moderx = $self->_rx('mode');
442              
443 513         732 foreach my $string (@strings) {
444 1010 100       7978 if ($string =~ s/\s*$moderx\s*//i) {
445 174         357 my $m = $1;
446 174 100       518 if ($$dmb{'data'}{'wordmatch'}{'mode'}{lc($m)} == 1) {
447 1         2 $m = 'standard';
448             } else {
449 173         193 $m = 'business';
450             }
451 174         190 $mode = $m;
452              
453 174         305 ($err,@delta) = $dmb->_split_delta($string);
454 174 100       454 last PARSE if (! $err);
455             }
456             }
457              
458             # Now we'll check each string for an expanded form delta.
459              
460 424         526 foreach my $string (@strings) {
461 794         892 my $past = 0;
462              
463 794         1062 my $whenrx = $self->_rx('when');
464 794 100 66     4817 if ($string &&
465             $string =~ s/$whenrx//i) {
466 50         130 my $when = $1;
467 50 100       176 if ($$dmb{'data'}{'wordmatch'}{'when'}{lc($when)} == 1) {
468 16         27 $past = 1;
469             }
470             }
471              
472 794         1149 my $rx = $self->_rx('expanded');
473 794 100 66     16452 if ($string &&
474             $string =~ $rx) {
475 114         1709 @delta = @+{qw(y m w d h mn s)};
476 114         337 foreach my $f (@delta) {
477 798 100       1330 if (! defined $f) {
    100          
478 660         686 $f = 0;
479             } elsif (exists $$dmb{'data'}{'wordmatch'}{'nth'}{lc($f)}) {
480 4         14 $f = $$dmb{'data'}{'wordmatch'}{'nth'}{lc($f)};
481             } else {
482 134         367 $f =~ s/\s//g;
483             }
484             }
485              
486             # if $past, reverse the signs
487 114 100       214 if ($past) {
488 12         21 foreach my $v (@delta) {
489 84         92 $v *= -1;
490             }
491             }
492              
493 114         308 last PARSE;
494             }
495             }
496             }
497              
498 773 100       1446 if (! @delta) {
499 310         422 $$self{'err'} = "[parse] Invalid delta string";
500 310         735 return 1;
501             }
502              
503             # If the string contains a mode string and the mode was passed in
504             # as an option, they must be identical.
505              
506 463 50 66     1545 if ($mode && exists($opts{'mode'}) && $mode ne $opts{'mode'}) {
      33        
507 0         0 $$self{'err'} =
508             "[parse] Mode option conflicts with mode specified in string";
509 0         0 return 1;
510             }
511 463 100       754 $mode = $opts{'mode'} if (exists $opts{'mode'});
512 463 100       907 $mode = 'standard' if (! $mode);
513              
514             # Figure out the type.
515              
516             my %o = ( 'source' => 'string',
517 463 50       1868 'nonorm' => (exists $opts{'nonorm'} ? $opts{'nonorm'} : 0),
518             'sign' => -1,
519             'mode' => $mode,
520             );
521              
522 463         1797 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
523 463         1072 my $type = $o{'type'};
524 463         588 my $type_from = $o{'type_from'};
525              
526 463 50       735 if ($err) {
527 0         0 $$self{'err'} = "[parse] $err";
528 0         0 return 1;
529             }
530              
531 463         911 $$self{'data'}{'in'} = $instring;
532 463         1193 $$self{'data'}{'delta'} = [@delta];
533 463         1079 $$self{'data'}{'mode'} = $mode;
534 463 50 33     1158 $$self{'data'}{'gotmode'} = ($mode || exists $opts{'mode'} ? 1 : 0);
535 463         596 $$self{'data'}{'type'} = $type;
536 463         630 $$self{'data'}{'type_from'} = $type_from;
537 463         751 $$self{'data'}{'length'} = 'unknown';
538 463 50       801 $$self{'data'}{'normalized'} = ($opts{'nonorm'} ? 0 : 1);
539              
540 463         1859 return 0;
541             }
542              
543             sub printf {
544 2350     2350 1 4248 my($self,@in) = @_;
545 2350 50       4478 if ($$self{'err'}) {
546 0         0 carp "WARNING: [printf] Object must contain a valid delta";
547 0         0 return undef;
548             }
549              
550 2350         2391 my($y,$M,$w,$d,$h,$m,$s) = @{ $$self{'data'}{'delta'} };
  2350         5793  
551              
552 2350         2749 my @out;
553 2350         3110 foreach my $in (@in) {
554 2352         2816 my $out = '';
555 2352         3546 while ($in) {
556 2812 100       22245 if ($in =~ s/^([^%]+)//) {
    100          
    100          
    100          
    100          
    50          
557 268         530 $out .= $1;
558              
559             } elsif ($in =~ s/^%%//) {
560 1         2 $out .= "%";
561              
562             } elsif ($in =~ s/^%
563             (\+)? # sign
564             ([<>0])? # pad
565             (\d+)? # width
566             ([yMwdhms]) # field
567             v # type
568             //ox) {
569 18         61 my($sign,$pad,$width,$field) = ($1,$2,$3,$4);
570 18         34 $out .= $self->_printf_field($sign,$pad,$width,0,$field);
571              
572             } elsif ($in =~ s/^(%
573             (\+)? # sign
574             ([<>0])? # pad
575             (\d+)? # width
576             (?:\.(\d+))? # precision
577             ([yMwdhms]) # field
578             ([yMwdhms]) # field0
579             ([yMwdhms]) # field1
580             )//ox) {
581 2503         13581 my($match,$sign,$pad,$width,$precision,$field,$field0,$field1) =
582             ($1,$2,$3,$4,$5,$6,$7,$8);
583              
584             # Get the list of fields we're expressing
585              
586 2503         5689 my @field = qw(y M w d h m s);
587 2503   66     7513 while (@field && $field[0] ne $field0) {
588 338         574 shift(@field);
589             }
590 2503   66     6510 while (@field && $field[$#field] ne $field1) {
591 342         620 pop(@field);
592             }
593              
594 2503 50       3735 if (! @field) {
595 0         0 $out .= $match;
596             } else {
597 2503         5407 $out .=
598             $self->_printf_field($sign,$pad,$width,$precision,$field,@field);
599             }
600              
601             } elsif ($in =~ s/^%
602             (\+)? # sign
603             ([<>])? # pad
604             (\d+)? # width
605             Dt
606             //ox) {
607 6         17 my($sign,$pad,$width) = ($1,$2,$3);
608 6         19 $out .= $self->_printf_delta($sign,$pad,$width,'y','s');
609              
610             } elsif ($in =~ s/^(%
611             (\+)? # sign
612             ([<>])? # pad
613             (\d+)? # width
614             D
615             ([yMwdhms]) # field0
616             ([yMwdhms]) # field1
617             )//ox) {
618 16         50 my($match,$sign,$pad,$width,$field0,$field1) = ($1,$2,$3,$4,$5,$6);
619              
620             # Get the list of fields we're expressing
621              
622 16         44 my @field = qw(y M w d h m s);
623 16   66     50 while (@field && $field[0] ne $field0) {
624 7         13 shift(@field);
625             }
626 16   66     40 while (@field && $field[$#field] ne $field1) {
627 59         102 pop(@field);
628             }
629              
630 16 50       21 if (! @field) {
631 0         0 $out .= $match;
632             } else {
633 16         33 $out .= $self->_printf_delta($sign,$pad,$width,$field[0],
634             $field[$#field]);
635             }
636              
637             } else {
638 0         0 $in =~ s/^(%[^%]*)//;
639 0         0 $out .= $1;
640             }
641             }
642 2352         4288 push(@out,$out);
643             }
644              
645 2350 100       4643 if (wantarray) {
    50          
646 58         234 return @out;
647             } elsif (@out == 1) {
648 2292         5813 return $out[0];
649             }
650              
651 0         0 return ''
652             }
653              
654             sub _printf_delta {
655 22     22   42 my($self,$sign,$pad,$width,$field0,$field1) = @_;
656 22         28 my $dmt = $$self{'tz'};
657 22         24 my $dmb = $$dmt{'base'};
658 22         23 my @delta = @{ $$self{'data'}{'delta'} };
  22         45  
659 22         22 my $delta;
660 22         93 my %tmp = qw(y 0 M 1 w 2 d 3 h 4 m 5 s 6);
661              
662             # Add a sign to each field
663              
664 22         26 my $s = "+";
665 22         27 foreach my $f (@delta) {
666 154 100       222 if ($f < 0) {
    100          
667 13         18 $s = "-";
668             } elsif ($f > 0) {
669 116         123 $s = "+";
670 116         91 $f *= 1;
671 116         122 $f = "+$f";
672             } else {
673 25         35 $f = "$s$f";
674             }
675             }
676              
677             # Split the delta into field sets containing only those fields to
678             # print.
679             #
680             # @set = ( [SETa] [SETb] ....)
681             # where [SETx] is a listref of fields from one set of fields
682              
683 22         20 my @set;
684 22         29 my $mode = $$self{'data'}{'mode'};
685              
686 22         26 my $f0 = $tmp{$field0};
687 22         22 my $f1 = $tmp{$field1};
688              
689 22 100       37 if ($field0 eq $field1) {
    100          
690 3         6 @set = ( [ $delta[$f0] ] );
691              
692             } elsif ($mode eq 'business') {
693              
694 4 100       8 if ($f0 <= 1) {
695             # if (field0 = y or M)
696             # add [y,M]
697             # if field1 = M
698             # done
699             # else
700             # field0 = w
701 3         9 push(@set, [ @delta[$f0..1] ]);
702 3 100       8 $f0 = ($f1 == 1 ? 7 : 2);
703             }
704              
705 4 100       6 if ($f0 == 2) {
706             # if (field0 = w)
707             # add [w]
708             # if field1 = w
709             # done
710             # else
711             # field0 = d
712 2         2 push(@set, [ $delta[2] ]);
713 2 50       5 $f0 = ($f1 == 2 ? 7 : 3);
714             }
715              
716 4 100       24 if ($f0 <= 6) {
717 3         11 push(@set, [ @delta[$f0..$f1] ]);
718             }
719              
720             } else {
721              
722 15 100       24 if ($f0 <= 1) {
723             # if (field0 = y or M)
724             # add [y,M]
725             # if field1 = M
726             # done
727             # else
728             # field0 = w
729 14         34 push(@set, [ @delta[$f0..1] ]);
730 14 100       26 $f0 = ($f1 == 1 ? 7 : 2);
731             }
732              
733 15 100       18 if ($f0 <= 3) {
734             # if (field0 = w or d)
735             # if (field1 = w or d)
736             # add [w ... [f1]]
737             # done
738             # else
739             # add [w,d]
740             # field0 = h
741 13 100       21 if ($f1 <= 3) {
742 7         11 push(@set, [ @delta[$f0..$f1] ]);
743 7         11 $f0 = 7;
744             } else {
745 6         36 push(@set, [ @delta[$f0..3] ]);
746 6         7 $f0 = 4;
747             }
748             }
749              
750 15 100       16 if ($f0 <= 6) {
751 6         13 push(@set, [ @delta[$f0..$f1] ]);
752             }
753             }
754              
755             # If we're not forcing signs, remove signs from all fields
756             # except the first in each set.
757              
758 22         22 my @ret;
759              
760 22         25 foreach my $set (@set) {
761 44         58 my @f = @$set;
762              
763 44 100 66     81 if (defined($sign) && $sign eq "+") {
764 16         25 push(@ret,@f);
765             } else {
766 28         45 push(@ret,shift(@f));
767 28         30 foreach my $f (@f) {
768 26         49 $f =~ s/[-+]//;
769 26         42 push(@ret,$f);
770             }
771             }
772             }
773              
774             # Width/pad
775              
776 22         43 my $ret = join(':',@ret);
777 22 100 100     38 if ($width && length($ret) < $width) {
778 3 100 100     10 if (defined $pad && $pad eq ">") {
779 1         4 $ret .= ' 'x($width-length($ret));
780             } else {
781 2         7 $ret = ' 'x($width-length($ret)) . $ret;
782             }
783             }
784              
785 22         104 return $ret;
786             }
787              
788             sub _printf_field {
789 2521     2521   6216 my($self,$sign,$pad,$width,$precision,$field,@field) = @_;
790              
791 2521         5118 my $val = $self->_printf_field_val($field,@field);
792 2521 100       4303 $pad = "<" if (! defined($pad));
793              
794             # Strip off the sign.
795              
796 2521         2801 my $s = '';
797              
798 2521 100       4873 if ($val < 0) {
    100          
799 66         90 $s = "-";
800 66         121 $val *= -1;
801             } elsif ($sign) {
802 16         15 $s = "+";
803             }
804              
805             # Handle the precision.
806              
807 2521 100       4597 if (defined($precision)) {
    50          
808 222         755 $val = sprintf("%.${precision}f",$val);
809              
810             } elsif (defined($width)) {
811 0         0 my $i = $s . int($val) . '.';
812 0 0       0 if (length($i) < $width) {
813 0         0 $precision = $width-length($i);
814 0         0 $val = sprintf("%.${precision}f",$val);
815             }
816             }
817              
818             # Handle padding.
819              
820 2521 100       3263 if ($width) {
821 38 100       56 if ($pad eq ">") {
    100          
822 8         10 $val = "$s$val";
823 8 100       16 my $pad = ($width > length($val) ? $width - length($val) : 0);
824 8         15 $val .= ' 'x$pad;
825              
826             } elsif ($pad eq "<") {
827 15         16 $val = "$s$val";
828 15 100       24 my $pad = ($width > length($val) ? $width - length($val) : 0);
829 15         23 $val = ' 'x$pad . $val;
830              
831             } else {
832 15 100       24 my $pad = ($width > length($val)-length($s) ?
833             $width - length($val) - length($s): 0);
834 15         27 $val = $s . '0'x$pad . $val;
835             }
836             } else {
837 2483         3669 $val = "$s$val";
838             }
839              
840 2521         9047 return $val;
841             }
842              
843             # $$self{'data'}{'f'}{X}{Y} is the value of field X expressed in terms of Y.
844             #
845             sub _printf_field_val {
846 2521     2521   5335 my($self,$field,@field) = @_;
847              
848 2521 50 66     8639 if (! exists $$self{'data'}{'f'}{'y'} &&
849             ! exists $$self{'data'}{'f'}{'y'}{'y'}) {
850              
851 1516         1696 my($yv,$Mv,$wv,$dv,$hv,$mv,$sv) = map { $_*1 } @{ $$self{'data'}{'delta'} };
  10612         12704  
  1516         3884  
852 1516         2804 $$self{'data'}{'f'}{'y'}{'y'} = $yv;
853 1516         2744 $$self{'data'}{'f'}{'M'}{'M'} = $Mv;
854 1516         2572 $$self{'data'}{'f'}{'w'}{'w'} = $wv;
855 1516         2797 $$self{'data'}{'f'}{'d'}{'d'} = $dv;
856 1516         2780 $$self{'data'}{'f'}{'h'}{'h'} = $hv;
857 1516         3419 $$self{'data'}{'f'}{'m'}{'m'} = $mv;
858 1516         3877 $$self{'data'}{'f'}{'s'}{'s'} = $sv;
859             }
860              
861             # A single field
862              
863 2521 100       3969 if (! @field) {
864 18         33 return $$self{'data'}{'f'}{$field}{$field};
865             }
866              
867             # Find the length of 1 unit of each field in terms of seconds.
868              
869 2503 100       4659 if (! exists $$self{'data'}{'flen'}{'s'}) {
870 1510         2132 my $mode = $$self{'data'}{'mode'};
871 1510         3998 my $dmb = $self->base();
872             $$self{'data'}{'flen'} = { 's' => 1,
873             'm' => 60,
874             'h' => 3600,
875             'd' => $$dmb{'data'}{'len'}{$mode}{'dl'},
876             'w' => $$dmb{'data'}{'len'}{$mode}{'wl'},
877             'M' => $$dmb{'data'}{'len'}{$mode}{'ml'},
878 1510         9795 'y' => $$dmb{'data'}{'len'}{$mode}{'yl'},
879             };
880             }
881              
882             # Calculate the value for each field.
883              
884 2503         2964 my $val = 0;
885 2503         3150 foreach my $f (@field) {
886              
887             # We want the value of $f expressed in terms of $field
888              
889 16841 100       25247 if (! exists $$self{'data'}{'f'}{$f}{$field}) {
890              
891             # Get the value of $f expressed in seconds
892              
893 9372 100       12776 if (! exists $$self{'data'}{'f'}{$f}{'s'}) {
894             $$self{'data'}{'f'}{$f}{'s'} =
895 9033         14524 $$self{'data'}{'f'}{$f}{$f} * $$self{'data'}{'flen'}{$f};
896             }
897              
898             # Get the value of $f expressed in terms of $field
899              
900             $$self{'data'}{'f'}{$f}{$field} =
901 9372         15436 $$self{'data'}{'f'}{$f}{'s'} / $$self{'data'}{'flen'}{$field};
902             }
903              
904 16841         21388 $val += $$self{'data'}{'f'}{$f}{$field};
905             }
906              
907 2503         4575 return $val;
908             }
909              
910             sub type {
911 54     54 1 3228 my($self,$op) = @_;
912 54         72 $op = lc($op);
913              
914 54 100 100     145 if ($op eq 'business' ||
915             $op eq 'standard') {
916 33 100       88 return ($$self{'data'}{'mode'} eq $op ? 1 : 0);
917             }
918              
919 21 100       59 return ($$self{'data'}{'type'} eq $op ? 1 : 0);
920             }
921              
922             sub calc {
923 29     29 1 117 my($self,$obj,@args) = @_;
924 29 50       95 if ($$self{'err'}) {
925 0         0 $$self{'err'} = "[calc] First object invalid (delta)";
926 0         0 return undef;
927             }
928              
929 29 50       84 if (ref($obj) eq 'Date::Manip::Date') {
    50          
930 0 0       0 if ($$obj{'err'}) {
931 0         0 $$self{'err'} = "[calc] Second object invalid (date)";
932 0         0 return undef;
933             }
934 0         0 return $obj->calc($self,@args);
935              
936             } elsif (ref($obj) eq 'Date::Manip::Delta') {
937 29 50       59 if ($$obj{'err'}) {
938 0         0 $$self{'err'} = "[calc] Second object invalid (delta)";
939 0         0 return undef;
940             }
941 29         141 return $self->_calc_delta_delta($obj,@args);
942              
943             } else {
944 0         0 $$self{'err'} = "[calc] Second object must be a Date/Delta object";
945 0         0 return undef;
946             }
947             }
948              
949             sub __type_max {
950 29     29   56 my($type1,$type2) = @_;
951 29 100       69 return $type1 if ($type1 eq $type2);
952 2         11 foreach my $type ('estimate','approx','semi') {
953 6 100 66     17 return $type if ($type1 eq $type || $type2 eq $type);
954             }
955 0         0 return 'exact';
956             }
957              
958             sub _calc_delta_delta {
959 29     29   55 my($self,$delta,@args) = @_;
960 29         45 my $dmt = $$self{'tz'};
961 29         39 my $dmb = $$dmt{'base'};
962 29         90 my $ret = $self->new_delta;
963              
964 29         41 my($subtract,$no_normalize);
965 29 50       57 if (@args > 2) {
966 0         0 $$ret{'err'} = "Unknown args in calc";
967 0         0 return $ret;
968             }
969              
970 29 50       69 if (@args == 2) {
    100          
971 0         0 ($subtract,$no_normalize) = @args;
972             } elsif (@args == 1) {
973 4 50       10 if ($args[0] eq 'nonormalize') {
974 0         0 $subtract = 0;
975 0         0 $no_normalize = 1;
976             } else {
977 4         7 $subtract = $args[0];
978 4         4 $no_normalize = 0;
979             }
980             } else {
981 25         29 $subtract = 0;
982 25         29 $no_normalize = 0;
983             }
984              
985 29 50       69 if ($$self{'data'}{'mode'} ne $$delta{'data'}{'mode'}) {
986 0         0 $$ret{'err'} = "[calc] Delta/delta calculation objects must be of " .
987             'the same mode';
988 0         0 return $ret;
989             }
990              
991 29         88 my ($err,@delta);
992 29         65 for (my $i=0; $i<7; $i++) {
993 203 100       226 if ($subtract) {
994 28         54 $delta[$i] = $$self{'data'}{'delta'}[$i] - $$delta{'data'}{'delta'}[$i];
995             } else {
996 175         388 $delta[$i] = $$self{'data'}{'delta'}[$i] + $$delta{'data'}{'delta'}[$i];
997             }
998             }
999              
1000             my $type = __type_max($$self{'data'}{'type'},
1001 29         89 $$delta{'data'}{'type'});
1002             my %o = ( 'source' => 'delta',
1003             'nonorm' => $no_normalize,
1004             'sign' => -1,
1005             'type' => $type,
1006 29         104 'mode' => $$self{'data'}{'mode'},
1007             );
1008              
1009 29         90 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
1010              
1011 29         74 $$ret{'data'}{'in'} = '';
1012 29         85 $$ret{'data'}{'delta'} = [@delta];
1013 29         153 $$ret{'data'}{'mode'} = $$self{'data'}{'mode'};
1014 29         45 $$ret{'data'}{'gotmode'} = 1;
1015 29         44 $$ret{'data'}{'type'} = $type;
1016 29         38 $$ret{'data'}{'type_from'} = 'det';
1017 29         39 $$ret{'data'}{'length'} = 'unknown';
1018 29         44 $$ret{'data'}{'normalized'} = 1-$no_normalize;
1019              
1020 29         151 return $ret;
1021             }
1022              
1023             sub convert {
1024 62     62 1 205 my($self,$to) = @_;
1025              
1026 62         187 my %mode_val = ( 'exact' => 0,
1027             'semi' => 1,
1028             'approx' => 2,
1029             'estimated' => 3,
1030             );
1031              
1032 62         104 my $from = $$self{'data'}{'type'};
1033 62         74 my $from_val = $mode_val{$from};
1034 62         71 my $to_val = $mode_val{$to};
1035              
1036 62 100       114 return if ($from_val == $to_val);
1037              
1038             #
1039             # Converting from exact to less exact
1040             #
1041              
1042 43 100       65 if ($from_val < $to_val) {
1043              
1044 35         119 $self->set( { 'nonorm' => 0,
1045             'type' => $to } );
1046 35         146 return;
1047             }
1048              
1049             #
1050             # Converting from less exact to more exact
1051             # *** DEPRECATE *** 7.00
1052             #
1053              
1054 8         10 my @fields;
1055             {
1056 170     170   1607 no integer;
  170         373  
  170         2151  
  8         10  
1057              
1058 8         44 my $dmb = $self->base();
1059 8         14 my $mode= $$self{'data'}{'mode'};
1060 8         17 my $yl = $$dmb{'data'}{'len'}{$mode}{'yl'};
1061 8         12 my $ml = $$dmb{'data'}{'len'}{$mode}{'ml'};
1062 8         9 my $wl = $$dmb{'data'}{'len'}{$mode}{'wl'};
1063 8         11 my $dl = $$dmb{'data'}{'len'}{$mode}{'dl'};
1064              
1065             # Convert it to seconds
1066              
1067 8         9 my($y,$m,$w,$d,$h,$mn,$s) = @{ $$self{'data'}{'delta'} };
  8         29  
1068 8         18 $s += $y*$yl + $m*$ml + $w*$wl + $d*$dl + $h*3600 + $mn*60;
1069              
1070 8         13 @fields = (0,0,0,0,0,0,$s);
1071              
1072 8 100       10 if ($mode eq 'business') {
1073              
1074 4 50 66     26 if ($to eq 'estimated') {
    100          
1075 0         0 @fields = $dmb->_normalize_bus_est(@fields);
1076              
1077             } elsif ($to eq 'approx' ||
1078             $to eq 'semi') {
1079 1         3 @fields = $dmb->_normalize_bus_approx(@fields);
1080              
1081             } else {
1082 3         7 @fields = $dmb->_normalize_bus_exact(@fields);
1083             }
1084              
1085             } else {
1086              
1087 4 50 66     16 if ($to eq 'estimated') {
    100          
1088 0         0 @fields = $dmb->_normalize_est(@fields);
1089              
1090             } elsif ($to eq 'approx' ||
1091             $to eq 'semi') {
1092 1         3 @fields = $dmb->_normalize_approx(@fields);
1093              
1094             } else {
1095 3         9 @fields = $dmb->_normalize_exact(@fields);
1096             }
1097              
1098             }
1099             }
1100              
1101 8         18 $$self{'data'}{'delta'} = [ @fields ];
1102 8         12 $$self{'data'}{'gotmode'} = 1;
1103 8         13 $$self{'data'}{'type'} = $to;
1104 8         8 $$self{'data'}{'type_from'} = 'opt';
1105 8         10 $$self{'data'}{'normalized'} = 1;
1106 8         22 $$self{'data'}{'length'} = 'unknown';
1107             }
1108              
1109             sub cmp {
1110 3     3 1 18 my($self,$delta) = @_;
1111              
1112 3 50       8 if ($$self{'err'}) {
1113 0         0 carp "WARNING: [cmp] Arguments must be valid deltas: delta1";
1114 0         0 return undef;
1115             }
1116              
1117 3 50       8 if (! (ref($delta) eq 'Date::Manip::Delta')) {
1118 0         0 carp "WARNING: [cmp] Argument must be a Date::Manip::Delta object";
1119 0         0 return undef;
1120             }
1121 3 50       6 if ($$delta{'err'}) {
1122 0         0 carp "WARNING: [cmp] Arguments must be valid deltas: delta2";
1123 0         0 return undef;
1124             }
1125              
1126 3 50       6 if ($$self{'data'}{'mode'} ne $$delta{'data'}{'mode'}) {
1127 0         0 carp "WARNING: [cmp] Deltas must both be business or standard";
1128 0         0 return undef;
1129             }
1130              
1131 3         5 my $mode = $$self{'data'}{'mode'};
1132 3         15 my $dmb = $self->base();
1133 3         7 my $yl = $$dmb{'data'}{'len'}{$mode}{'yl'};
1134 3         3 my $ml = $$dmb{'data'}{'len'}{$mode}{'ml'};
1135 3         4 my $wl = $$dmb{'data'}{'len'}{$mode}{'wl'};
1136 3         4 my $dl = $$dmb{'data'}{'len'}{$mode}{'dl'};
1137              
1138 3 50       7 if ($$self{'data'}{'length'} eq 'unknown') {
1139 3         3 my($y,$m,$w,$d,$h,$mn,$s) = @{ $$self{'data'}{'delta'} };
  3         11  
1140              
1141 170     170   88977 no integer;
  170         442  
  170         935  
1142 3         8 $$self{'data'}{'length'} = int($y*$yl + $m*$ml + $w*$wl +
1143             $d*$dl + $h*3600 + $mn*60 + $s);
1144             }
1145              
1146 3 50       4 if ($$delta{'data'}{'length'} eq 'unknown') {
1147 3         5 my($y,$m,$w,$d,$h,$mn,$s) = @{ $$delta{'data'}{'delta'} };
  3         6  
1148              
1149 170     170   19044 no integer;
  170         397  
  170         715  
1150 3         5 $$delta{'data'}{'length'} = int($y*$yl + $m*$ml + $w*$wl +
1151             $d*$dl + $h*3600 + $mn*60 + $s);
1152             }
1153              
1154 3         12 return ($$self{'data'}{'length'} <=> $$delta{'data'}{'length'});
1155             }
1156              
1157             1;
1158             # Local Variables:
1159             # mode: cperl
1160             # indent-tabs-mode: nil
1161             # cperl-indent-level: 3
1162             # cperl-continued-statement-offset: 2
1163             # cperl-continued-brace-offset: 0
1164             # cperl-brace-offset: 0
1165             # cperl-brace-imaginary-offset: 0
1166             # cperl-label-offset: 0
1167             # End: