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-2023 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 168     168   1202 use Date::Manip::Obj;
  168         331  
  168         8883  
15             @ISA = ('Date::Manip::Obj');
16              
17             require 5.010000;
18 168     168   909 use warnings;
  168         315  
  168         4743  
19 168     168   839 use strict;
  168         296  
  168         3635  
20 168     168   839 use utf8;
  168         357  
  168         1111  
21 168     168   4637 use IO::File;
  168         385  
  168         31184  
22 168     168   1154 use Carp;
  168         330  
  168         10028  
23             #use re 'debug';
24              
25 168     168   1182 use Date::Manip::Base;
  168         459  
  168         4515  
26 168     168   947 use Date::Manip::TZ;
  168         326  
  168         282600  
27              
28             our $VERSION;
29             $VERSION='6.91';
30 168     168   1085 END { undef $VERSION; }
31              
32             ########################################################################
33             # BASE METHODS
34             ########################################################################
35              
36             sub is_delta {
37 1     1 1 191 return 1;
38             }
39              
40             sub config {
41 15     15 1 137 my($self,@args) = @_;
42 15         156 $self->SUPER::config(@args);
43              
44             # A new config can change the value of the format fields, so clear them.
45 15         63 $$self{'data'}{'f'} = {};
46 15         66 $$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 12026     12026   20840 my($self) = @_;
54              
55 12026         26811 my $def = [0,0,0,0,0,0,0];
56 12026         19698 my $dmt = $$self{'tz'};
57 12026         17960 my $dmb = $$dmt{'base'};
58              
59 12026         18513 $$self{'err'} = '';
60 12026         96029 $$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   4 my($self) = @_;
83              
84 2         6 my @args = @{ $$self{'args'} };
  2         4  
85 2         8 $self->parse(@args);
86             }
87              
88             sub value {
89 3422     3422 1 7511 my($self,$as_input) = @_;
90              
91 3422 50       7091 if ($$self{'err'}) {
92 0 0       0 return () if (wantarray);
93 0         0 return '';
94             }
95              
96 3422         5410 my $dmt = $$self{'tz'};
97 3422         5083 my $dmb = $$dmt{'base'};
98              
99 3422         4574 my @delta = @{ $$self{'data'}{'delta'} };
  3422         8943  
100              
101 3422 100       13046 return @delta if (wantarray);
102 339         459 my $err;
103              
104             my %o = ( 'source' => 'delta',
105             'nonorm' => 1,
106             'type' => $$self{'data'}{'type'},
107             'sign' => 0,
108 339         1271 'mode' => $$self{'data'}{'mode'},
109             );
110              
111 339         1181 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
112 339         2419 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 168     168   727696 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 148154 my($self,@args) = @_;
129 4988         7409 my %opts;
130 4988 100       10239 if (ref($args[0]) eq 'HASH') {
131 120         182 %opts = %{ $args[0] };
  120         597  
132             } else {
133             # *** DEPRECATED 7.0 ***
134 4868 100       9579 if (@args == 3) {
135 15 50       46 %opts = ( $args[0] => $args[1],
136             'nonorm' => ($args[2] ? 1 : 0) );
137             } else {
138 4853         11198 %opts = ( $args[0] => $args[1] );
139             }
140             }
141              
142             # Check for some invalid opts
143              
144 4988         13936 foreach my $key (keys %opts) {
145 5164         8760 my $val = $opts{$key};
146 5164         9134 delete $opts{$key};
147              
148             # *** DEPRECATED 7.0 ***
149 5164 50       11968 $key = 'standard' if (lc($key) eq 'normal');
150              
151 5164 100 100     16193 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 5154 100       10936 if (exists $opts{lc($key)}) {
159 1         3 $key = lc($key);
160 1         6 $$self{'err'} = "[set] Invalid option: $key entered twice";
161 1         6 return 1;
162             }
163              
164 5153         12195 $opts{lc($key)} = $val;
165              
166             } elsif ($key =~ /^[yMwdhms]$/) {
167              
168 9         28 $opts{$key} = $val;
169              
170             } else {
171 1         6 $$self{'err'} = "[set] Unknown option: $key";
172 1         5 return 1;
173             }
174             }
175              
176 4986 100 66     52072 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         7 $$self{'err'} = "[set] Fields set multiple times";
184 2         8 return 1;
185             }
186              
187 4984 100 100     11983 if (exists $opts{'mode'} && $opts{'mode'} !~ /^(business|standard)$/) {
188 1         4 $$self{'err'} = "[set] Unknown value for mode: $opts{mode}";
189 1         4 return 1;
190             }
191 4983 100 100     10955 if (exists $opts{'type'} &&
192             $opts{'type'} !~ /^(exact|semi|estimated|approx)$/) {
193 1         4 $$self{'err'} = "[set] Unknown value for type: $opts{type}";
194 1         4 return 1;
195             }
196              
197 4982 100       15303 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         309 $opts{'delta'} = $opts{'business'};
205 166         329 $opts{'mode'} = 'business';
206             } elsif (exists $opts{'standard'}) {
207 8         20 $opts{'delta'} = $opts{'standard'};
208 8         17 $opts{'mode'} = 'standard';
209             }
210              
211             # If we are setting delta/business/standard, we need to initialize
212             # all the parameters.
213              
214 4981         6885 my @delta;
215 4981 100       9175 if (exists $opts{'delta'}) {
216 4938 100       11631 if (ref($opts{'delta'}) ne 'ARRAY') {
217 1         4 $$self{'err'} = "[set] Option delta requires an array value";
218 1         5 return 1;
219             }
220              
221             # Init everything because we're setting an entire new delta
222 4937         12446 $self->_init();
223 4937         9110 @delta = @{ $opts{'delta'} };
  4937         12255  
224              
225             } else {
226 43         63 @delta = @{ $$self{'data'}{'delta'} };
  43         120  
227             }
228              
229             # Figure out the parameters. Include the nonorm/mode/type
230             # options.
231              
232 4980         7307 my $err;
233 4980         7255 my $dmt = $$self{'tz'};
234 4980         6750 my $dmb = $$dmt{'base'};
235 4980 100       10616 my $gotmode = (exists $opts{'mode'} ? 1 : $$self{'data'}{'gotmode'});
236             my $mode = (exists $opts{'mode'} ? $opts{'mode'} :
237 4980 100       9904 $$self{'data'}{'mode'});
238 4980 100       8782 my $nonorm = (exists $opts{'nonorm'} ? $opts{'nonorm'} : 0);
239              
240 4980         7329 my ($type,$type_from);
241 4980 100       8199 if (exists $opts{'type'}) {
242 94         141 $type = $opts{'type'};
243 94         144 $type_from = 'opt';
244             } else {
245 4886         7349 $type = $$self{'data'}{'type'};
246 4886         7365 $type_from = $$self{'data'}{'type_from'};
247             }
248              
249             # If we're setting individual fields, do that now
250              
251             {
252 4980         6919 my $field_set = 0;
  4980         6598  
253              
254             # Check all individual fields
255 4980         9045 foreach my $opt (qw(y M w d h m s)) {
256 34848 100       61028 if (exists $opts{$opt}) {
257 8 100       26 if (ref($opts{$opt})) {
258 1         4 $$self{'err'} = "[set] Option $opt requires a scalar value";
259 1         4 return 1;
260             }
261 7         15 my $val = $opts{$opt};
262 7 100       23 if (! $dmb->_is_num($val)) {
263 1         4 $$self{'err'} = "[set] Option $opt requires a numerical value";
264 1         5 return 1;
265             }
266 6         17 $delta[ $f{$opt} ] = $val;
267 6         16 $field_set = 1;
268             }
269             }
270              
271             # If none were set, than we're done with setting.
272 4978 100       11165 last if (! $field_set);
273              
274 6 50       15 if ($$self{'err'}) {
275 0         0 return 1;
276             }
277             }
278              
279             # Check that the type is consistent with @delta.
280              
281 4978         16672 ($err,$type,$type_from) =
282             $dmb->_check_delta_type($mode,$type,$type_from,@delta);
283              
284 4978 100       11135 if ($err) {
285 11         29 $$self{'err'} = "[set] $err";
286 11         44 return 1;
287             }
288              
289 4967         18974 my %o = ( 'source' => 'delta',
290             'nonorm' => $nonorm,
291             'type' => $type,
292             'sign' => -1,
293             'mode' => $mode,
294             );
295              
296 4967         19768 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
297              
298 4967 100       13523 if ($err) {
299 2         9 $$self{'err'} = "[set] $err";
300 2         8 return 1;
301             }
302              
303 4965         15313 $$self{'data'}{'delta'} = [ @delta ];
304 4965         8965 $$self{'data'}{'mode'} = $mode;
305 4965         7645 $$self{'data'}{'gotmode'} = $gotmode;
306 4965         7691 $$self{'data'}{'type'} = $type;
307 4965         7730 $$self{'data'}{'type_from'} = $type_from;
308 4965         8243 $$self{'data'}{'normalized'} = 1-$nonorm;
309 4965         8370 $$self{'data'}{'length'} = 'unknown';
310 4965         7700 $$self{'data'}{'in'} = '';
311              
312 4965         20971 return 0;
313             }
314             }
315              
316             sub _rx {
317 2101     2101   3497 my($self,$rx) = @_;
318 2101         3030 my $dmt = $$self{'tz'};
319 2101         2747 my $dmb = $$dmt{'base'};
320              
321             return $$dmb{'data'}{'rx'}{'delta'}{$rx}
322 2101 100       5895 if (exists $$dmb{'data'}{'rx'}{'delta'}{$rx});
323              
324 84 100       487 if ($rx eq 'expanded') {
    100          
    50          
325 26         76 my $sign = '[-+]?\s*';
326 26         67 my $sep = '(?:,\s*|\s+|$)';
327              
328 26         92 my $nth = $$dmb{'data'}{'rx'}{'nth'}[0];
329 26         100 my $yf = $$dmb{data}{rx}{fields}[1];
330 26         71 my $mf = $$dmb{data}{rx}{fields}[2];
331 26         77 my $wf = $$dmb{data}{rx}{fields}[3];
332 26         72 my $df = $$dmb{data}{rx}{fields}[4];
333 26         78 my $hf = $$dmb{data}{rx}{fields}[5];
334 26         74 my $mnf = $$dmb{data}{rx}{fields}[6];
335 26         73 my $sf = $$dmb{data}{rx}{fields}[7];
336 26         60 my $num = '(?:\d+(?:\.\d*)?|\.\d+)';
337              
338 26         239 my $y = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$yf)$sep)";
339 26         201 my $m = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$mf)$sep)";
340 26         196 my $w = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$wf)$sep)";
341 26         196 my $d = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$df)$sep)";
342 26         242 my $h = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$hf)$sep)";
343 26         250 my $mn = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$mnf)$sep)";
344 26         219 my $s = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$sf)?)";
345              
346 26         147491 my $exprx = qr/^\s*$y?$m?$w?$d?$h?$mn?$s?\s*$/i;
347 26         1912 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $exprx;
348              
349             } elsif ($rx eq 'mode') {
350              
351 32         801 my $mode = qr/\b($$dmb{'data'}{'rx'}{'mode'}[0])\b/i;
352 32         161 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $mode;
353              
354             } elsif ($rx eq 'when') {
355              
356 26         1447 my $when = qr/\b($$dmb{'data'}{'rx'}{'when'}[0])\b/i;
357 26         147 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $when;
358              
359             }
360              
361 84         298 return $$dmb{'data'}{'rx'}{'delta'}{$rx};
362             }
363              
364             sub parse {
365 773     773 1 213332 my($self,$instring,@args) = @_;
366 773         2010 $self->_init();
367              
368 773         1469 my %opts;
369 773 50       1861 if (ref($args[0]) eq 'HASH') {
370 0         0 %opts = %{ $args[0] };
  0         0  
371              
372             } else {
373             # *** DEPRECATED 7.0 ***
374              
375 773         1198 my($business,$no_normalize);
376              
377 773 50       2657 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         3 my $arg = lc($args[0]);
389 1 50       7 if ($arg eq 'standard') {
    50          
    0          
    0          
390 0         0 $opts{'mode'} = 'standard';
391             } elsif ($arg eq 'business') {
392 1         3 $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         1414 my $dmt = $$self{'tz'};
408 773         1227 my $dmb = $$dmt{'base'};
409 773         1839 $self->_init();
410              
411 773 50       1827 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         1241 my ($err,@delta,$mode);
424 773         1129 $mode = '';
425 773         1125 $$self{'err'} = '';
426 773         3266 $instring =~ s/^\s*//;
427 773         4226 $instring =~ s/\s*$//;
428              
429             PARSE: {
430              
431             # First, we'll try the standard format (without a mode string)
432              
433 773         1363 ($err,@delta) = $dmb->_split_delta($instring);
  773         2392  
434 773 100       1988 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         1477 my @strings = $dmb->_encoding($instring);
441 513         1496 my $moderx = $self->_rx('mode');
442              
443 513         982 foreach my $string (@strings) {
444 1010 100       9649 if ($string =~ s/\s*$moderx\s*//i) {
445 174         485 my $m = $1;
446 174 100       627 if ($$dmb{'data'}{'wordmatch'}{'mode'}{lc($m)} == 1) {
447 1         3 $m = 'standard';
448             } else {
449 173         290 $m = 'business';
450             }
451 174         266 $mode = $m;
452              
453 174         412 ($err,@delta) = $dmb->_split_delta($string);
454 174 100       597 last PARSE if (! $err);
455             }
456             }
457              
458             # Now we'll check each string for an expanded form delta.
459              
460 424         894 foreach my $string (@strings) {
461 794         1241 my $past = 0;
462              
463 794         1528 my $whenrx = $self->_rx('when');
464 794 100 66     6150 if ($string &&
465             $string =~ s/$whenrx//i) {
466 50         145 my $when = $1;
467 50 100       237 if ($$dmb{'data'}{'wordmatch'}{'when'}{lc($when)} == 1) {
468 16         39 $past = 1;
469             }
470             }
471              
472 794         1665 my $rx = $self->_rx('expanded');
473 794 100 66     15796 if ($string &&
474             $string =~ $rx) {
475 114         1717 @delta = @+{qw(y m w d h mn s)};
476 114         438 foreach my $f (@delta) {
477 798 100       1564 if (! defined $f) {
    100          
478 660         943 $f = 0;
479             } elsif (exists $$dmb{'data'}{'wordmatch'}{'nth'}{lc($f)}) {
480 4         17 $f = $$dmb{'data'}{'wordmatch'}{'nth'}{lc($f)};
481             } else {
482 134         434 $f =~ s/\s//g;
483             }
484             }
485              
486             # if $past, reverse the signs
487 114 100       261 if ($past) {
488 12         38 foreach my $v (@delta) {
489 84         120 $v *= -1;
490             }
491             }
492              
493 114         325 last PARSE;
494             }
495             }
496             }
497              
498 773 100       2078 if (! @delta) {
499 310         622 $$self{'err'} = "[parse] Invalid delta string";
500 310         1143 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     1293 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       1001 $mode = $opts{'mode'} if (exists $opts{'mode'});
512 463 100       955 $mode = 'standard' if (! $mode);
513              
514             # Figure out the type.
515              
516             my %o = ( 'source' => 'string',
517 463 50       1919 'nonorm' => (exists $opts{'nonorm'} ? $opts{'nonorm'} : 0),
518             'sign' => -1,
519             'mode' => $mode,
520             );
521              
522 463         2133 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
523 463         1266 my $type = $o{'type'};
524 463         743 my $type_from = $o{'type_from'};
525              
526 463 50       928 if ($err) {
527 0         0 $$self{'err'} = "[parse] $err";
528 0         0 return 1;
529             }
530              
531 463         1048 $$self{'data'}{'in'} = $instring;
532 463         1374 $$self{'data'}{'delta'} = [@delta];
533 463         877 $$self{'data'}{'mode'} = $mode;
534 463 50 33     1344 $$self{'data'}{'gotmode'} = ($mode || exists $opts{'mode'} ? 1 : 0);
535 463         749 $$self{'data'}{'type'} = $type;
536 463         712 $$self{'data'}{'type_from'} = $type_from;
537 463         764 $$self{'data'}{'length'} = 'unknown';
538 463 50       977 $$self{'data'}{'normalized'} = ($opts{'nonorm'} ? 0 : 1);
539              
540 463         1888 return 0;
541             }
542              
543             sub printf {
544 2350     2350 1 5163 my($self,@in) = @_;
545 2350 50       5585 if ($$self{'err'}) {
546 0         0 carp "WARNING: [printf] Object must contain a valid delta";
547 0         0 return undef;
548             }
549              
550 2350         3266 my($y,$M,$w,$d,$h,$m,$s) = @{ $$self{'data'}{'delta'} };
  2350         6981  
551              
552 2350         3531 my @out;
553 2350         4124 foreach my $in (@in) {
554 2352         3545 my $out = '';
555 2352         4550 while ($in) {
556 2812 100       25048 if ($in =~ s/^([^%]+)//) {
    100          
    100          
    100          
    100          
    50          
557 268         702 $out .= $1;
558              
559             } elsif ($in =~ s/^%%//) {
560 1         4 $out .= "%";
561              
562             } elsif ($in =~ s/^%
563             (\+)? # sign
564             ([<>0])? # pad
565             (\d+)? # width
566             ([yMwdhms]) # field
567             v # type
568             //ox) {
569 18         55 my($sign,$pad,$width,$field) = ($1,$2,$3,$4);
570 18         46 $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         13295 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         6597 my @field = qw(y M w d h m s);
587 2503   66     10306 while (@field && $field[0] ne $field0) {
588 338         848 shift(@field);
589             }
590 2503   66     8999 while (@field && $field[$#field] ne $field1) {
591 342         872 pop(@field);
592             }
593              
594 2503 50       4957 if (! @field) {
595 0         0 $out .= $match;
596             } else {
597 2503         6278 $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         20 my($sign,$pad,$width) = ($1,$2,$3);
608 6         17 $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         66 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         41 my @field = qw(y M w d h m s);
623 16   66     68 while (@field && $field[0] ne $field0) {
624 7         21 shift(@field);
625             }
626 16   66     55 while (@field && $field[$#field] ne $field1) {
627 59         155 pop(@field);
628             }
629              
630 16 50       30 if (! @field) {
631 0         0 $out .= $match;
632             } else {
633 16         39 $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         5209 push(@out,$out);
643             }
644              
645 2350 100       6203 if (wantarray) {
    50          
646 58         232 return @out;
647             } elsif (@out == 1) {
648 2292         7606 return $out[0];
649             }
650              
651 0         0 return ''
652             }
653              
654             sub _printf_delta {
655 22     22   46 my($self,$sign,$pad,$width,$field0,$field1) = @_;
656 22         38 my $dmt = $$self{'tz'};
657 22         30 my $dmb = $$dmt{'base'};
658 22         30 my @delta = @{ $$self{'data'}{'delta'} };
  22         53  
659 22         28 my $delta;
660 22         118 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         35 my $s = "+";
665 22         33 foreach my $f (@delta) {
666 154 100       281 if ($f < 0) {
    100          
667 13         20 $s = "-";
668             } elsif ($f > 0) {
669 116         143 $s = "+";
670 116         134 $f *= 1;
671 116         214 $f = "+$f";
672             } else {
673 25         44 $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         34 my @set;
684 22         34 my $mode = $$self{'data'}{'mode'};
685              
686 22         36 my $f0 = $tmp{$field0};
687 22         33 my $f1 = $tmp{$field1};
688              
689 22 100       47 if ($field0 eq $field1) {
    100          
690 3         8 @set = ( [ $delta[$f0] ] );
691              
692             } elsif ($mode eq 'business') {
693              
694 4 100       10 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         10 push(@set, [ @delta[$f0..1] ]);
702 3 100       8 $f0 = ($f1 == 1 ? 7 : 2);
703             }
704              
705 4 100       10 if ($f0 == 2) {
706             # if (field0 = w)
707             # add [w]
708             # if field1 = w
709             # done
710             # else
711             # field0 = d
712 2         5 push(@set, [ $delta[2] ]);
713 2 50       4 $f0 = ($f1 == 2 ? 7 : 3);
714             }
715              
716 4 100       9 if ($f0 <= 6) {
717 3         13 push(@set, [ @delta[$f0..$f1] ]);
718             }
719              
720             } else {
721              
722 15 100       34 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         39 push(@set, [ @delta[$f0..1] ]);
730 14 100       30 $f0 = ($f1 == 1 ? 7 : 2);
731             }
732              
733 15 100       28 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       25 if ($f1 <= 3) {
742 7         15 push(@set, [ @delta[$f0..$f1] ]);
743 7         13 $f0 = 7;
744             } else {
745 6         13 push(@set, [ @delta[$f0..3] ]);
746 6         12 $f0 = 4;
747             }
748             }
749              
750 15 100       24 if ($f0 <= 6) {
751 6         14 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         30 my @ret;
759              
760 22         36 foreach my $set (@set) {
761 44         92 my @f = @$set;
762              
763 44 100 66     100 if (defined($sign) && $sign eq "+") {
764 16         33 push(@ret,@f);
765             } else {
766 28         38 push(@ret,shift(@f));
767 28         58 foreach my $f (@f) {
768 26         71 $f =~ s/[-+]//;
769 26         64 push(@ret,$f);
770             }
771             }
772             }
773              
774             # Width/pad
775              
776 22         63 my $ret = join(':',@ret);
777 22 100 100     55 if ($width && length($ret) < $width) {
778 3 100 100     11 if (defined $pad && $pad eq ">") {
779 1         5 $ret .= ' 'x($width-length($ret));
780             } else {
781 2         7 $ret = ' 'x($width-length($ret)) . $ret;
782             }
783             }
784              
785 22         124 return $ret;
786             }
787              
788             sub _printf_field {
789 2521     2521   7478 my($self,$sign,$pad,$width,$precision,$field,@field) = @_;
790              
791 2521         5544 my $val = $self->_printf_field_val($field,@field);
792 2521 100       5755 $pad = "<" if (! defined($pad));
793              
794             # Strip off the sign.
795              
796 2521         3905 my $s = '';
797              
798 2521 100       6105 if ($val < 0) {
    100          
799 66         138 $s = "-";
800 66         118 $val *= -1;
801             } elsif ($sign) {
802 16         22 $s = "+";
803             }
804              
805             # Handle the precision.
806              
807 2521 100       5667 if (defined($precision)) {
    50          
808 222         1035 $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       4283 if ($width) {
821 38 100       76 if ($pad eq ">") {
    100          
822 8         15 $val = "$s$val";
823 8 100       23 my $pad = ($width > length($val) ? $width - length($val) : 0);
824 8         18 $val .= ' 'x$pad;
825              
826             } elsif ($pad eq "<") {
827 15         30 $val = "$s$val";
828 15 100       32 my $pad = ($width > length($val) ? $width - length($val) : 0);
829 15         33 $val = ' 'x$pad . $val;
830              
831             } else {
832 15 100       35 my $pad = ($width > length($val)-length($s) ?
833             $width - length($val) - length($s): 0);
834 15         32 $val = $s . '0'x$pad . $val;
835             }
836             } else {
837 2483         5428 $val = "$s$val";
838             }
839              
840 2521         10515 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   6068 my($self,$field,@field) = @_;
847              
848 2521 50 66     10803 if (! exists $$self{'data'}{'f'}{'y'} &&
849             ! exists $$self{'data'}{'f'}{'y'}{'y'}) {
850              
851 1516         2337 my($yv,$Mv,$wv,$dv,$hv,$mv,$sv) = map { $_*1 } @{ $$self{'data'}{'delta'} };
  10612         16451  
  1516         3848  
852 1516         3684 $$self{'data'}{'f'}{'y'}{'y'} = $yv;
853 1516         3380 $$self{'data'}{'f'}{'M'}{'M'} = $Mv;
854 1516         3145 $$self{'data'}{'f'}{'w'}{'w'} = $wv;
855 1516         3654 $$self{'data'}{'f'}{'d'}{'d'} = $dv;
856 1516         3127 $$self{'data'}{'f'}{'h'}{'h'} = $hv;
857 1516         3216 $$self{'data'}{'f'}{'m'}{'m'} = $mv;
858 1516         3436 $$self{'data'}{'f'}{'s'}{'s'} = $sv;
859             }
860              
861             # A single field
862              
863 2521 100       5189 if (! @field) {
864 18         43 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       5984 if (! exists $$self{'data'}{'flen'}{'s'}) {
870 1510         2790 my $mode = $$self{'data'}{'mode'};
871 1510         4971 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         9371 'y' => $$dmb{'data'}{'len'}{$mode}{'yl'},
879             };
880             }
881              
882             # Calculate the value for each field.
883              
884 2503         3834 my $val = 0;
885 2503         4342 foreach my $f (@field) {
886              
887             # We want the value of $f expressed in terms of $field
888              
889 16841 100       32647 if (! exists $$self{'data'}{'f'}{$f}{$field}) {
890              
891             # Get the value of $f expressed in seconds
892              
893 9372 100       16620 if (! exists $$self{'data'}{'f'}{$f}{'s'}) {
894             $$self{'data'}{'f'}{$f}{'s'} =
895 9033         17513 $$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         18915 $$self{'data'}{'f'}{$f}{'s'} / $$self{'data'}{'flen'}{$field};
902             }
903              
904 16841         27858 $val += $$self{'data'}{'f'}{$f}{$field};
905             }
906              
907 2503         5480 return $val;
908             }
909              
910             sub type {
911 54     54 1 3856 my($self,$op) = @_;
912 54         84 $op = lc($op);
913              
914 54 100 100     156 if ($op eq 'business' ||
915             $op eq 'standard') {
916 33 100       110 return ($$self{'data'}{'mode'} eq $op ? 1 : 0);
917             }
918              
919 21 100       83 return ($$self{'data'}{'type'} eq $op ? 1 : 0);
920             }
921              
922             sub calc {
923 29     29 1 176 my($self,$obj,@args) = @_;
924 29 50       75 if ($$self{'err'}) {
925 0         0 $$self{'err'} = "[calc] First object invalid (delta)";
926 0         0 return undef;
927             }
928              
929 29 50       125 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       124 if ($$obj{'err'}) {
938 0         0 $$self{'err'} = "[calc] Second object invalid (delta)";
939 0         0 return undef;
940             }
941 29         90 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   67 my($type1,$type2) = @_;
951 29 100       84 return $type1 if ($type1 eq $type2);
952 2         7 foreach my $type ('estimate','approx','semi') {
953 6 100 66     22 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   65 my($self,$delta,@args) = @_;
960 29         48 my $dmt = $$self{'tz'};
961 29         52 my $dmb = $$dmt{'base'};
962 29         116 my $ret = $self->new_delta;
963              
964 29         68 my($subtract,$no_normalize);
965 29 50       80 if (@args > 2) {
966 0         0 $$ret{'err'} = "Unknown args in calc";
967 0         0 return $ret;
968             }
969              
970 29 50       104 if (@args == 2) {
    100          
971 0         0 ($subtract,$no_normalize) = @args;
972             } elsif (@args == 1) {
973 4 50       13 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         10 $no_normalize = 0;
979             }
980             } else {
981 25         44 $subtract = 0;
982 25         37 $no_normalize = 0;
983             }
984              
985 29 50       129 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         57 my ($err,@delta);
992 29         77 for (my $i=0; $i<7; $i++) {
993 203 100       283 if ($subtract) {
994 28         62 $delta[$i] = $$self{'data'}{'delta'}[$i] - $$delta{'data'}{'delta'}[$i];
995             } else {
996 175         405 $delta[$i] = $$self{'data'}{'delta'}[$i] + $$delta{'data'}{'delta'}[$i];
997             }
998             }
999              
1000             my $type = __type_max($$self{'data'}{'type'},
1001 29         132 $$delta{'data'}{'type'});
1002             my %o = ( 'source' => 'delta',
1003             'nonorm' => $no_normalize,
1004             'sign' => -1,
1005             'type' => $type,
1006 29         138 'mode' => $$self{'data'}{'mode'},
1007             );
1008              
1009 29         112 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
1010              
1011 29         99 $$ret{'data'}{'in'} = '';
1012 29         89 $$ret{'data'}{'delta'} = [@delta];
1013 29         61 $$ret{'data'}{'mode'} = $$self{'data'}{'mode'};
1014 29         56 $$ret{'data'}{'gotmode'} = 1;
1015 29         54 $$ret{'data'}{'type'} = $type;
1016 29         52 $$ret{'data'}{'type_from'} = 'det';
1017 29         49 $$ret{'data'}{'length'} = 'unknown';
1018 29         70 $$ret{'data'}{'normalized'} = 1-$no_normalize;
1019              
1020 29         170 return $ret;
1021             }
1022              
1023             sub convert {
1024 62     62 1 270 my($self,$to) = @_;
1025              
1026 62         196 my %mode_val = ( 'exact' => 0,
1027             'semi' => 1,
1028             'approx' => 2,
1029             'estimated' => 3,
1030             );
1031              
1032 62         98 my $from = $$self{'data'}{'type'};
1033 62         97 my $from_val = $mode_val{$from};
1034 62         97 my $to_val = $mode_val{$to};
1035              
1036 62 100       144 return if ($from_val == $to_val);
1037              
1038             #
1039             # Converting from exact to less exact
1040             #
1041              
1042 43 100       84 if ($from_val < $to_val) {
1043              
1044 35         151 $self->set( { 'nonorm' => 0,
1045             'type' => $to } );
1046 35         144 return;
1047             }
1048              
1049             #
1050             # Converting from less exact to more exact
1051             # *** DEPRECATE *** 7.00
1052             #
1053              
1054 8         14 my @fields;
1055             {
1056 168     168   1683 no integer;
  168         492  
  168         1731  
  8         14  
1057              
1058 8         51 my $dmb = $self->base();
1059 8         20 my $mode= $$self{'data'}{'mode'};
1060 8         22 my $yl = $$dmb{'data'}{'len'}{$mode}{'yl'};
1061 8         16 my $ml = $$dmb{'data'}{'len'}{$mode}{'ml'};
1062 8         32 my $wl = $$dmb{'data'}{'len'}{$mode}{'wl'};
1063 8         16 my $dl = $$dmb{'data'}{'len'}{$mode}{'dl'};
1064              
1065             # Convert it to seconds
1066              
1067 8         14 my($y,$m,$w,$d,$h,$mn,$s) = @{ $$self{'data'}{'delta'} };
  8         22  
1068 8         24 $s += $y*$yl + $m*$ml + $w*$wl + $d*$dl + $h*3600 + $mn*60;
1069              
1070 8         15 @fields = (0,0,0,0,0,0,$s);
1071              
1072 8 100       20 if ($mode eq 'business') {
1073              
1074 4 50 66     34 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         9 @fields = $dmb->_normalize_bus_approx(@fields);
1080              
1081             } else {
1082 3         13 @fields = $dmb->_normalize_bus_exact(@fields);
1083             }
1084              
1085             } else {
1086              
1087 4 50 66     23 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         8 @fields = $dmb->_normalize_exact(@fields);
1096             }
1097              
1098             }
1099             }
1100              
1101 8         33 $$self{'data'}{'delta'} = [ @fields ];
1102 8         13 $$self{'data'}{'gotmode'} = 1;
1103 8         16 $$self{'data'}{'type'} = $to;
1104 8         15 $$self{'data'}{'type_from'} = 'opt';
1105 8         13 $$self{'data'}{'normalized'} = 1;
1106 8         23 $$self{'data'}{'length'} = 'unknown';
1107             }
1108              
1109             sub cmp {
1110 3     3 1 19 my($self,$delta) = @_;
1111              
1112 3 50       7 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       7 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       7 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         4 my $mode = $$self{'data'}{'mode'};
1132 3         18 my $dmb = $self->base();
1133 3         7 my $yl = $$dmb{'data'}{'len'}{$mode}{'yl'};
1134 3         6 my $ml = $$dmb{'data'}{'len'}{$mode}{'ml'};
1135 3         3 my $wl = $$dmb{'data'}{'len'}{$mode}{'wl'};
1136 3         6 my $dl = $$dmb{'data'}{'len'}{$mode}{'dl'};
1137              
1138 3 50       7 if ($$self{'data'}{'length'} eq 'unknown') {
1139 3         4 my($y,$m,$w,$d,$h,$mn,$s) = @{ $$self{'data'}{'delta'} };
  3         7  
1140              
1141 168     168   89170 no integer;
  168         451  
  168         997  
1142 3         9 $$self{'data'}{'length'} = int($y*$yl + $m*$ml + $w*$wl +
1143             $d*$dl + $h*3600 + $mn*60 + $s);
1144             }
1145              
1146 3 50       31 if ($$delta{'data'}{'length'} eq 'unknown') {
1147 3         4 my($y,$m,$w,$d,$h,$mn,$s) = @{ $$delta{'data'}{'delta'} };
  3         7  
1148              
1149 168     168   18977 no integer;
  168         434  
  168         844  
1150 3         7 $$delta{'data'}{'length'} = int($y*$yl + $m*$ml + $w*$wl +
1151             $d*$dl + $h*3600 + $mn*60 + $s);
1152             }
1153              
1154 3         13 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: