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-2022 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   1236 use Date::Manip::Obj;
  168         342  
  168         8755  
15             @ISA = ('Date::Manip::Obj');
16              
17             require 5.010000;
18 168     168   937 use warnings;
  168         317  
  168         4648  
19 168     168   797 use strict;
  168         313  
  168         3549  
20 168     168   840 use utf8;
  168         352  
  168         1139  
21 168     168   4772 use IO::File;
  168         371  
  168         31585  
22 168     168   1175 use Carp;
  168         350  
  168         9938  
23             #use re 'debug';
24              
25 168     168   1138 use Date::Manip::Base;
  168         396  
  168         4866  
26 168     168   972 use Date::Manip::TZ;
  168         423  
  168         297454  
27              
28             our $VERSION;
29             $VERSION='6.90';
30 168     168   1053 END { undef $VERSION; }
31              
32             ########################################################################
33             # BASE METHODS
34             ########################################################################
35              
36             sub is_delta {
37 1     1 1 200 return 1;
38             }
39              
40             sub config {
41 15     15 1 124 my($self,@args) = @_;
42 15         140 $self->SUPER::config(@args);
43              
44             # A new config can change the value of the format fields, so clear them.
45 15         57 $$self{'data'}{'f'} = {};
46 15         75 $$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   20754 my($self) = @_;
54              
55 12026         27417 my $def = [0,0,0,0,0,0,0];
56 12026         19752 my $dmt = $$self{'tz'};
57 12026         17373 my $dmb = $$dmt{'base'};
58              
59 12026         18042 $$self{'err'} = '';
60 12026         98531 $$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   5 my($self) = @_;
83              
84 2         3 my @args = @{ $$self{'args'} };
  2         5  
85 2         8 $self->parse(@args);
86             }
87              
88             sub value {
89 3422     3422 1 7604 my($self,$as_input) = @_;
90              
91 3422 50       7425 if ($$self{'err'}) {
92 0 0       0 return () if (wantarray);
93 0         0 return '';
94             }
95              
96 3422         5040 my $dmt = $$self{'tz'};
97 3422         5163 my $dmb = $$dmt{'base'};
98              
99 3422         4520 my @delta = @{ $$self{'data'}{'delta'} };
  3422         8864  
100              
101 3422 100       13006 return @delta if (wantarray);
102 339         4399 my $err;
103              
104             my %o = ( 'source' => 'delta',
105             'nonorm' => 1,
106             'type' => $$self{'data'}{'type'},
107             'sign' => 0,
108 339         1715 'mode' => $$self{'data'}{'mode'},
109             );
110              
111 339         1208 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
112 339         2414 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   755883 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 150595 my($self,@args) = @_;
129 4988         7331 my %opts;
130 4988 100       10246 if (ref($args[0]) eq 'HASH') {
131 120         156 %opts = %{ $args[0] };
  120         548  
132             } else {
133             # *** DEPRECATED 7.0 ***
134 4868 100       9783 if (@args == 3) {
135 15 50       52 %opts = ( $args[0] => $args[1],
136             'nonorm' => ($args[2] ? 1 : 0) );
137             } else {
138 4853         11304 %opts = ( $args[0] => $args[1] );
139             }
140             }
141              
142             # Check for some invalid opts
143              
144 4988         13933 foreach my $key (keys %opts) {
145 5164         8640 my $val = $opts{$key};
146 5164         8840 delete $opts{$key};
147              
148             # *** DEPRECATED 7.0 ***
149 5164 50       11744 $key = 'standard' if (lc($key) eq 'normal');
150              
151 5164 100 100     16129 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       11421 if (exists $opts{lc($key)}) {
159 1         3 $key = lc($key);
160 1         7 $$self{'err'} = "[set] Invalid option: $key entered twice";
161 1         3 return 1;
162             }
163              
164 5153         12249 $opts{lc($key)} = $val;
165              
166             } elsif ($key =~ /^[yMwdhms]$/) {
167              
168 9         36 $opts{$key} = $val;
169              
170             } else {
171 1         4 $$self{'err'} = "[set] Unknown option: $key";
172 1         5 return 1;
173             }
174             }
175              
176 4986 100 66     52036 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         11 $$self{'err'} = "[set] Fields set multiple times";
184 2         9 return 1;
185             }
186              
187 4984 100 100     11965 if (exists $opts{'mode'} && $opts{'mode'} !~ /^(business|standard)$/) {
188 1         4 $$self{'err'} = "[set] Unknown value for mode: $opts{mode}";
189 1         3 return 1;
190             }
191 4983 100 100     10882 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       15436 if ( (exists $opts{'business'}) +
    100          
    100          
198             (exists $opts{'standard'}) +
199             (exists $opts{'mode'})
200             > 1 ) {
201 1         5 $$self{'err'} = "[set] Mode set multiple times";
202 1         4 return 1;
203             } elsif (exists $opts{'business'}) {
204 166         314 $opts{'delta'} = $opts{'business'};
205 166         316 $opts{'mode'} = 'business';
206             } elsif (exists $opts{'standard'}) {
207 8         22 $opts{'delta'} = $opts{'standard'};
208 8         20 $opts{'mode'} = 'standard';
209             }
210              
211             # If we are setting delta/business/standard, we need to initialize
212             # all the parameters.
213              
214 4981         6820 my @delta;
215 4981 100       8937 if (exists $opts{'delta'}) {
216 4938 100       11877 if (ref($opts{'delta'}) ne 'ARRAY') {
217 1         5 $$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         12131 $self->_init();
223 4937         9156 @delta = @{ $opts{'delta'} };
  4937         12511  
224              
225             } else {
226 43         66 @delta = @{ $$self{'data'}{'delta'} };
  43         128  
227             }
228              
229             # Figure out the parameters. Include the nonorm/mode/type
230             # options.
231              
232 4980         7230 my $err;
233 4980         7299 my $dmt = $$self{'tz'};
234 4980         7484 my $dmb = $$dmt{'base'};
235 4980 100       10336 my $gotmode = (exists $opts{'mode'} ? 1 : $$self{'data'}{'gotmode'});
236             my $mode = (exists $opts{'mode'} ? $opts{'mode'} :
237 4980 100       10462 $$self{'data'}{'mode'});
238 4980 100       9498 my $nonorm = (exists $opts{'nonorm'} ? $opts{'nonorm'} : 0);
239              
240 4980         7536 my ($type,$type_from);
241 4980 100       8477 if (exists $opts{'type'}) {
242 94         128 $type = $opts{'type'};
243 94         135 $type_from = 'opt';
244             } else {
245 4886         7567 $type = $$self{'data'}{'type'};
246 4886         7521 $type_from = $$self{'data'}{'type_from'};
247             }
248              
249             # If we're setting individual fields, do that now
250              
251             {
252 4980         6881 my $field_set = 0;
  4980         6778  
253              
254             # Check all individual fields
255 4980         9007 foreach my $opt (qw(y M w d h m s)) {
256 34848 100       60321 if (exists $opts{$opt}) {
257 8 100       23 if (ref($opts{$opt})) {
258 1         4 $$self{'err'} = "[set] Option $opt requires a scalar value";
259 1         6 return 1;
260             }
261 7         15 my $val = $opts{$opt};
262 7 100       28 if (! $dmb->_is_num($val)) {
263 1         5 $$self{'err'} = "[set] Option $opt requires a numerical value";
264 1         5 return 1;
265             }
266 6         23 $delta[ $f{$opt} ] = $val;
267 6         13 $field_set = 1;
268             }
269             }
270              
271             # If none were set, than we're done with setting.
272 4978 100       11374 last if (! $field_set);
273              
274 6 50       17 if ($$self{'err'}) {
275 0         0 return 1;
276             }
277             }
278              
279             # Check that the type is consistent with @delta.
280              
281 4978         16438 ($err,$type,$type_from) =
282             $dmb->_check_delta_type($mode,$type,$type_from,@delta);
283              
284 4978 100       10625 if ($err) {
285 11         34 $$self{'err'} = "[set] $err";
286 11         41 return 1;
287             }
288              
289 4967         18611 my %o = ( 'source' => 'delta',
290             'nonorm' => $nonorm,
291             'type' => $type,
292             'sign' => -1,
293             'mode' => $mode,
294             );
295              
296 4967         20293 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
297              
298 4967 100       13532 if ($err) {
299 2         8 $$self{'err'} = "[set] $err";
300 2         9 return 1;
301             }
302              
303 4965         15248 $$self{'data'}{'delta'} = [ @delta ];
304 4965         8771 $$self{'data'}{'mode'} = $mode;
305 4965         7562 $$self{'data'}{'gotmode'} = $gotmode;
306 4965         7804 $$self{'data'}{'type'} = $type;
307 4965         7168 $$self{'data'}{'type_from'} = $type_from;
308 4965         8151 $$self{'data'}{'normalized'} = 1-$nonorm;
309 4965         8127 $$self{'data'}{'length'} = 'unknown';
310 4965         7914 $$self{'data'}{'in'} = '';
311              
312 4965         20445 return 0;
313             }
314             }
315              
316             sub _rx {
317 2101     2101   3541 my($self,$rx) = @_;
318 2101         3095 my $dmt = $$self{'tz'};
319 2101         2756 my $dmb = $$dmt{'base'};
320              
321             return $$dmb{'data'}{'rx'}{'delta'}{$rx}
322 2101 100       5965 if (exists $$dmb{'data'}{'rx'}{'delta'}{$rx});
323              
324 84 100       532 if ($rx eq 'expanded') {
    100          
    50          
325 26         81 my $sign = '[-+]?\s*';
326 26         90 my $sep = '(?:,\s*|\s+|$)';
327              
328 26         117 my $nth = $$dmb{'data'}{'rx'}{'nth'}[0];
329 26         86 my $yf = $$dmb{data}{rx}{fields}[1];
330 26         75 my $mf = $$dmb{data}{rx}{fields}[2];
331 26         72 my $wf = $$dmb{data}{rx}{fields}[3];
332 26         75 my $df = $$dmb{data}{rx}{fields}[4];
333 26         76 my $hf = $$dmb{data}{rx}{fields}[5];
334 26         73 my $mnf = $$dmb{data}{rx}{fields}[6];
335 26         76 my $sf = $$dmb{data}{rx}{fields}[7];
336 26         64 my $num = '(?:\d+(?:\.\d*)?|\.\d+)';
337              
338 26         228 my $y = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$yf)$sep)";
339 26         215 my $m = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$mf)$sep)";
340 26         193 my $w = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$wf)$sep)";
341 26         174 my $d = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$df)$sep)";
342 26         195 my $h = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$hf)$sep)";
343 26         257 my $mn = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$mnf)$sep)";
344 26         269 my $s = "(?:(?:(?$sign$num)|(?$nth))\\s*(?:$sf)?)";
345              
346 26         146139 my $exprx = qr/^\s*$y?$m?$w?$d?$h?$mn?$s?\s*$/i;
347 26         1929 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $exprx;
348              
349             } elsif ($rx eq 'mode') {
350              
351 32         845 my $mode = qr/\b($$dmb{'data'}{'rx'}{'mode'}[0])\b/i;
352 32         171 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $mode;
353              
354             } elsif ($rx eq 'when') {
355              
356 26         1590 my $when = qr/\b($$dmb{'data'}{'rx'}{'when'}[0])\b/i;
357 26         166 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $when;
358              
359             }
360              
361 84         288 return $$dmb{'data'}{'rx'}{'delta'}{$rx};
362             }
363              
364             sub parse {
365 773     773 1 215124 my($self,$instring,@args) = @_;
366 773         2049 $self->_init();
367              
368 773         1400 my %opts;
369 773 50       1885 if (ref($args[0]) eq 'HASH') {
370 0         0 %opts = %{ $args[0] };
  0         0  
371              
372             } else {
373             # *** DEPRECATED 7.0 ***
374              
375 773         1199 my($business,$no_normalize);
376              
377 773 50       2728 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         4 my $arg = lc($args[0]);
389 1 50       10 if ($arg eq 'standard') {
    50          
    0          
    0          
390 0         0 $opts{'mode'} = 'standard';
391             } elsif ($arg eq 'business') {
392 1         4 $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         1484 my $dmt = $$self{'tz'};
408 773         1173 my $dmb = $$dmt{'base'};
409 773         1785 $self->_init();
410              
411 773 50       1942 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         1277 my ($err,@delta,$mode);
424 773         1215 $mode = '';
425 773         1144 $$self{'err'} = '';
426 773         3314 $instring =~ s/^\s*//;
427 773         4280 $instring =~ s/\s*$//;
428              
429             PARSE: {
430              
431             # First, we'll try the standard format (without a mode string)
432              
433 773         1324 ($err,@delta) = $dmb->_split_delta($instring);
  773         2370  
434 773 100       2030 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         1540 my @strings = $dmb->_encoding($instring);
441 513         1533 my $moderx = $self->_rx('mode');
442              
443 513         1011 foreach my $string (@strings) {
444 1010 100       10184 if ($string =~ s/\s*$moderx\s*//i) {
445 174         475 my $m = $1;
446 174 100       668 if ($$dmb{'data'}{'wordmatch'}{'mode'}{lc($m)} == 1) {
447 1         3 $m = 'standard';
448             } else {
449 173         282 $m = 'business';
450             }
451 174         259 $mode = $m;
452              
453 174         424 ($err,@delta) = $dmb->_split_delta($string);
454 174 100       608 last PARSE if (! $err);
455             }
456             }
457              
458             # Now we'll check each string for an expanded form delta.
459              
460 424         867 foreach my $string (@strings) {
461 794         1244 my $past = 0;
462              
463 794         1484 my $whenrx = $self->_rx('when');
464 794 100 66     6317 if ($string &&
465             $string =~ s/$whenrx//i) {
466 50         153 my $when = $1;
467 50 100       227 if ($$dmb{'data'}{'wordmatch'}{'when'}{lc($when)} == 1) {
468 16         35 $past = 1;
469             }
470             }
471              
472 794         1704 my $rx = $self->_rx('expanded');
473 794 100 66     15601 if ($string &&
474             $string =~ $rx) {
475 114         1730 @delta = @+{qw(y m w d h mn s)};
476 114         502 foreach my $f (@delta) {
477 798 100       1653 if (! defined $f) {
    100          
478 660         956 $f = 0;
479             } elsif (exists $$dmb{'data'}{'wordmatch'}{'nth'}{lc($f)}) {
480 4         15 $f = $$dmb{'data'}{'wordmatch'}{'nth'}{lc($f)};
481             } else {
482 134         452 $f =~ s/\s//g;
483             }
484             }
485              
486             # if $past, reverse the signs
487 114 100       285 if ($past) {
488 12         34 foreach my $v (@delta) {
489 84         119 $v *= -1;
490             }
491             }
492              
493 114         341 last PARSE;
494             }
495             }
496             }
497              
498 773 100       1953 if (! @delta) {
499 310         656 $$self{'err'} = "[parse] Invalid delta string";
500 310         1068 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     1327 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       1047 $mode = $opts{'mode'} if (exists $opts{'mode'});
512 463 100       1005 $mode = 'standard' if (! $mode);
513              
514             # Figure out the type.
515              
516             my %o = ( 'source' => 'string',
517 463 50       2030 'nonorm' => (exists $opts{'nonorm'} ? $opts{'nonorm'} : 0),
518             'sign' => -1,
519             'mode' => $mode,
520             );
521              
522 463         2210 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
523 463         1253 my $type = $o{'type'};
524 463         760 my $type_from = $o{'type_from'};
525              
526 463 50       950 if ($err) {
527 0         0 $$self{'err'} = "[parse] $err";
528 0         0 return 1;
529             }
530              
531 463         1128 $$self{'data'}{'in'} = $instring;
532 463         1397 $$self{'data'}{'delta'} = [@delta];
533 463         870 $$self{'data'}{'mode'} = $mode;
534 463 50 33     1336 $$self{'data'}{'gotmode'} = ($mode || exists $opts{'mode'} ? 1 : 0);
535 463         767 $$self{'data'}{'type'} = $type;
536 463         718 $$self{'data'}{'type_from'} = $type_from;
537 463         778 $$self{'data'}{'length'} = 'unknown';
538 463 50       1034 $$self{'data'}{'normalized'} = ($opts{'nonorm'} ? 0 : 1);
539              
540 463         1833 return 0;
541             }
542              
543             sub printf {
544 2350     2350 1 5111 my($self,@in) = @_;
545 2350 50       5633 if ($$self{'err'}) {
546 0         0 carp "WARNING: [printf] Object must contain a valid delta";
547 0         0 return undef;
548             }
549              
550 2350         3250 my($y,$M,$w,$d,$h,$m,$s) = @{ $$self{'data'}{'delta'} };
  2350         7282  
551              
552 2350         3624 my @out;
553 2350         4284 foreach my $in (@in) {
554 2352         3681 my $out = '';
555 2352         4666 while ($in) {
556 2812 100       25593 if ($in =~ s/^([^%]+)//) {
    100          
    100          
    100          
    100          
    50          
557 268         702 $out .= $1;
558              
559             } elsif ($in =~ s/^%%//) {
560 1         3 $out .= "%";
561              
562             } elsif ($in =~ s/^%
563             (\+)? # sign
564             ([<>0])? # pad
565             (\d+)? # width
566             ([yMwdhms]) # field
567             v # type
568             //ox) {
569 18         57 my($sign,$pad,$width,$field) = ($1,$2,$3,$4);
570 18         48 $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         13162 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         6785 my @field = qw(y M w d h m s);
587 2503   66     10452 while (@field && $field[0] ne $field0) {
588 338         899 shift(@field);
589             }
590 2503   66     9278 while (@field && $field[$#field] ne $field1) {
591 342         860 pop(@field);
592             }
593              
594 2503 50       4932 if (! @field) {
595 0         0 $out .= $match;
596             } else {
597 2503         6495 $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         19 my($sign,$pad,$width) = ($1,$2,$3);
608 6         16 $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         63 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         42 my @field = qw(y M w d h m s);
623 16   66     60 while (@field && $field[0] ne $field0) {
624 7         23 shift(@field);
625             }
626 16   66     56 while (@field && $field[$#field] ne $field1) {
627 59         157 pop(@field);
628             }
629              
630 16 50       26 if (! @field) {
631 0         0 $out .= $match;
632             } else {
633 16         43 $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         5348 push(@out,$out);
643             }
644              
645 2350 100       6479 if (wantarray) {
    50          
646 58         269 return @out;
647             } elsif (@out == 1) {
648 2292         7568 return $out[0];
649             }
650              
651 0         0 return ''
652             }
653              
654             sub _printf_delta {
655 22     22   48 my($self,$sign,$pad,$width,$field0,$field1) = @_;
656 22         35 my $dmt = $$self{'tz'};
657 22         33 my $dmb = $$dmt{'base'};
658 22         29 my @delta = @{ $$self{'data'}{'delta'} };
  22         50  
659 22         35 my $delta;
660 22         91 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         29 my $s = "+";
665 22         39 foreach my $f (@delta) {
666 154 100       268 if ($f < 0) {
    100          
667 13         19 $s = "-";
668             } elsif ($f > 0) {
669 116         137 $s = "+";
670 116         133 $f *= 1;
671 116         197 $f = "+$f";
672             } else {
673 25         63 $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         33 my @set;
684 22         32 my $mode = $$self{'data'}{'mode'};
685              
686 22         34 my $f0 = $tmp{$field0};
687 22         30 my $f1 = $tmp{$field1};
688              
689 22 100       50 if ($field0 eq $field1) {
    100          
690 3         8 @set = ( [ $delta[$f0] ] );
691              
692             } elsif ($mode eq 'business') {
693              
694 4 100       11 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       9 $f0 = ($f1 == 1 ? 7 : 2);
703             }
704              
705 4 100       11 if ($f0 == 2) {
706             # if (field0 = w)
707             # add [w]
708             # if field1 = w
709             # done
710             # else
711             # field0 = d
712 2         3 push(@set, [ $delta[2] ]);
713 2 50       5 $f0 = ($f1 == 2 ? 7 : 3);
714             }
715              
716 4 100       12 if ($f0 <= 6) {
717 3         12 push(@set, [ @delta[$f0..$f1] ]);
718             }
719              
720             } else {
721              
722 15 100       33 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       31 $f0 = ($f1 == 1 ? 7 : 2);
731             }
732              
733 15 100       27 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       24 if ($f1 <= 3) {
742 7         18 push(@set, [ @delta[$f0..$f1] ]);
743 7         10 $f0 = 7;
744             } else {
745 6         14 push(@set, [ @delta[$f0..3] ]);
746 6         8 $f0 = 4;
747             }
748             }
749              
750 15 100       30 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         29 my @ret;
759              
760 22         36 foreach my $set (@set) {
761 44         81 my @f = @$set;
762              
763 44 100 66     108 if (defined($sign) && $sign eq "+") {
764 16         35 push(@ret,@f);
765             } else {
766 28         36 push(@ret,shift(@f));
767 28         50 foreach my $f (@f) {
768 26         66 $f =~ s/[-+]//;
769 26         66 push(@ret,$f);
770             }
771             }
772             }
773              
774             # Width/pad
775              
776 22         52 my $ret = join(':',@ret);
777 22 100 100     52 if ($width && length($ret) < $width) {
778 3 100 100     13 if (defined $pad && $pad eq ">") {
779 1         5 $ret .= ' 'x($width-length($ret));
780             } else {
781 2         8 $ret = ' 'x($width-length($ret)) . $ret;
782             }
783             }
784              
785 22         152 return $ret;
786             }
787              
788             sub _printf_field {
789 2521     2521   7737 my($self,$sign,$pad,$width,$precision,$field,@field) = @_;
790              
791 2521         5643 my $val = $self->_printf_field_val($field,@field);
792 2521 100       5748 $pad = "<" if (! defined($pad));
793              
794             # Strip off the sign.
795              
796 2521         3690 my $s = '';
797              
798 2521 100       6153 if ($val < 0) {
    100          
799 66         140 $s = "-";
800 66         130 $val *= -1;
801             } elsif ($sign) {
802 16         24 $s = "+";
803             }
804              
805             # Handle the precision.
806              
807 2521 100       5854 if (defined($precision)) {
    50          
808 222         1410 $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       4292 if ($width) {
821 38 100       104 if ($pad eq ">") {
    100          
822 8         17 $val = "$s$val";
823 8 100       19 my $pad = ($width > length($val) ? $width - length($val) : 0);
824 8         18 $val .= ' 'x$pad;
825              
826             } elsif ($pad eq "<") {
827 15         27 $val = "$s$val";
828 15 100       36 my $pad = ($width > length($val) ? $width - length($val) : 0);
829 15         30 $val = ' 'x$pad . $val;
830              
831             } else {
832 15 100       34 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         5505 $val = "$s$val";
838             }
839              
840 2521         10824 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   6240 my($self,$field,@field) = @_;
847              
848 2521 50 66     11398 if (! exists $$self{'data'}{'f'}{'y'} &&
849             ! exists $$self{'data'}{'f'}{'y'}{'y'}) {
850              
851 1516         2396 my($yv,$Mv,$wv,$dv,$hv,$mv,$sv) = map { $_*1 } @{ $$self{'data'}{'delta'} };
  10612         16680  
  1516         3977  
852 1516         3691 $$self{'data'}{'f'}{'y'}{'y'} = $yv;
853 1516         3483 $$self{'data'}{'f'}{'M'}{'M'} = $Mv;
854 1516         3100 $$self{'data'}{'f'}{'w'}{'w'} = $wv;
855 1516         3292 $$self{'data'}{'f'}{'d'}{'d'} = $dv;
856 1516         3052 $$self{'data'}{'f'}{'h'}{'h'} = $hv;
857 1516         3145 $$self{'data'}{'f'}{'m'}{'m'} = $mv;
858 1516         3455 $$self{'data'}{'f'}{'s'}{'s'} = $sv;
859             }
860              
861             # A single field
862              
863 2521 100       5331 if (! @field) {
864 18         39 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       6080 if (! exists $$self{'data'}{'flen'}{'s'}) {
870 1510         2769 my $mode = $$self{'data'}{'mode'};
871 1510         4655 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         9584 'y' => $$dmb{'data'}{'len'}{$mode}{'yl'},
879             };
880             }
881              
882             # Calculate the value for each field.
883              
884 2503         3965 my $val = 0;
885 2503         4386 foreach my $f (@field) {
886              
887             # We want the value of $f expressed in terms of $field
888              
889 16841 100       33925 if (! exists $$self{'data'}{'f'}{$f}{$field}) {
890              
891             # Get the value of $f expressed in seconds
892              
893 9372 100       16948 if (! exists $$self{'data'}{'f'}{$f}{'s'}) {
894             $$self{'data'}{'f'}{$f}{'s'} =
895 9033         17895 $$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         20128 $$self{'data'}{'f'}{$f}{'s'} / $$self{'data'}{'flen'}{$field};
902             }
903              
904 16841         27965 $val += $$self{'data'}{'f'}{$f}{$field};
905             }
906              
907 2503         5599 return $val;
908             }
909              
910             sub type {
911 54     54 1 4165 my($self,$op) = @_;
912 54         92 $op = lc($op);
913              
914 54 100 100     156 if ($op eq 'business' ||
915             $op eq 'standard') {
916 33 100       119 return ($$self{'data'}{'mode'} eq $op ? 1 : 0);
917             }
918              
919 21 100       78 return ($$self{'data'}{'type'} eq $op ? 1 : 0);
920             }
921              
922             sub calc {
923 29     29 1 175 my($self,$obj,@args) = @_;
924 29 50       81 if ($$self{'err'}) {
925 0         0 $$self{'err'} = "[calc] First object invalid (delta)";
926 0         0 return undef;
927             }
928              
929 29 50       136 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       81 if ($$obj{'err'}) {
938 0         0 $$self{'err'} = "[calc] Second object invalid (delta)";
939 0         0 return undef;
940             }
941 29         97 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   89 my($type1,$type2) = @_;
951 29 100       89 return $type1 if ($type1 eq $type2);
952 2         6 foreach my $type ('estimate','approx','semi') {
953 6 100 66     21 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   66 my($self,$delta,@args) = @_;
960 29         45 my $dmt = $$self{'tz'};
961 29         52 my $dmb = $$dmt{'base'};
962 29         115 my $ret = $self->new_delta;
963              
964 29         59 my($subtract,$no_normalize);
965 29 50       140 if (@args > 2) {
966 0         0 $$ret{'err'} = "Unknown args in calc";
967 0         0 return $ret;
968             }
969              
970 29 50       101 if (@args == 2) {
    100          
971 0         0 ($subtract,$no_normalize) = @args;
972             } elsif (@args == 1) {
973 4 50       21 if ($args[0] eq 'nonormalize') {
974 0         0 $subtract = 0;
975 0         0 $no_normalize = 1;
976             } else {
977 4         14 $subtract = $args[0];
978 4         6 $no_normalize = 0;
979             }
980             } else {
981 25         42 $subtract = 0;
982 25         39 $no_normalize = 0;
983             }
984              
985 29 50       97 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         46 my ($err,@delta);
992 29         97 for (my $i=0; $i<7; $i++) {
993 203 100       296 if ($subtract) {
994 28         72 $delta[$i] = $$self{'data'}{'delta'}[$i] - $$delta{'data'}{'delta'}[$i];
995             } else {
996 175         403 $delta[$i] = $$self{'data'}{'delta'}[$i] + $$delta{'data'}{'delta'}[$i];
997             }
998             }
999              
1000             my $type = __type_max($$self{'data'}{'type'},
1001 29         123 $$delta{'data'}{'type'});
1002             my %o = ( 'source' => 'delta',
1003             'nonorm' => $no_normalize,
1004             'sign' => -1,
1005             'type' => $type,
1006 29         139 'mode' => $$self{'data'}{'mode'},
1007             );
1008              
1009 29         126 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
1010              
1011 29         97 $$ret{'data'}{'in'} = '';
1012 29         98 $$ret{'data'}{'delta'} = [@delta];
1013 29         71 $$ret{'data'}{'mode'} = $$self{'data'}{'mode'};
1014 29         57 $$ret{'data'}{'gotmode'} = 1;
1015 29         59 $$ret{'data'}{'type'} = $type;
1016 29         44 $$ret{'data'}{'type_from'} = 'det';
1017 29         57 $$ret{'data'}{'length'} = 'unknown';
1018 29         51 $$ret{'data'}{'normalized'} = 1-$no_normalize;
1019              
1020 29         169 return $ret;
1021             }
1022              
1023             sub convert {
1024 62     62 1 221 my($self,$to) = @_;
1025              
1026 62         207 my %mode_val = ( 'exact' => 0,
1027             'semi' => 1,
1028             'approx' => 2,
1029             'estimated' => 3,
1030             );
1031              
1032 62         103 my $from = $$self{'data'}{'type'};
1033 62         92 my $from_val = $mode_val{$from};
1034 62         89 my $to_val = $mode_val{$to};
1035              
1036 62 100       143 return if ($from_val == $to_val);
1037              
1038             #
1039             # Converting from exact to less exact
1040             #
1041              
1042 43 100       97 if ($from_val < $to_val) {
1043              
1044 35         136 $self->set( { 'nonorm' => 0,
1045             'type' => $to } );
1046 35         132 return;
1047             }
1048              
1049             #
1050             # Converting from less exact to more exact
1051             # *** DEPRECATE *** 7.00
1052             #
1053              
1054 8         16 my @fields;
1055             {
1056 168     168   1754 no integer;
  168         458  
  168         1766  
  8         14  
1057              
1058 8         37 my $dmb = $self->base();
1059 8         16 my $mode= $$self{'data'}{'mode'};
1060 8         23 my $yl = $$dmb{'data'}{'len'}{$mode}{'yl'};
1061 8         12 my $ml = $$dmb{'data'}{'len'}{$mode}{'ml'};
1062 8         15 my $wl = $$dmb{'data'}{'len'}{$mode}{'wl'};
1063 8         15 my $dl = $$dmb{'data'}{'len'}{$mode}{'dl'};
1064              
1065             # Convert it to seconds
1066              
1067 8         15 my($y,$m,$w,$d,$h,$mn,$s) = @{ $$self{'data'}{'delta'} };
  8         22  
1068 8         21 $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       19 if ($mode eq 'business') {
1073              
1074 4 50 66     32 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         13 @fields = $dmb->_normalize_bus_approx(@fields);
1080              
1081             } else {
1082 3         12 @fields = $dmb->_normalize_bus_exact(@fields);
1083             }
1084              
1085             } else {
1086              
1087 4 50 66     19 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         10 @fields = $dmb->_normalize_approx(@fields);
1093              
1094             } else {
1095 3         8 @fields = $dmb->_normalize_exact(@fields);
1096             }
1097              
1098             }
1099             }
1100              
1101 8         29 $$self{'data'}{'delta'} = [ @fields ];
1102 8         13 $$self{'data'}{'gotmode'} = 1;
1103 8         16 $$self{'data'}{'type'} = $to;
1104 8         14 $$self{'data'}{'type_from'} = 'opt';
1105 8         11 $$self{'data'}{'normalized'} = 1;
1106 8         23 $$self{'data'}{'length'} = 'unknown';
1107             }
1108              
1109             sub cmp {
1110 3     3 1 16 my($self,$delta) = @_;
1111              
1112 3 50       9 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       9 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       10 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         6 my $mode = $$self{'data'}{'mode'};
1132 3         23 my $dmb = $self->base();
1133 3         9 my $yl = $$dmb{'data'}{'len'}{$mode}{'yl'};
1134 3         6 my $ml = $$dmb{'data'}{'len'}{$mode}{'ml'};
1135 3         5 my $wl = $$dmb{'data'}{'len'}{$mode}{'wl'};
1136 3         7 my $dl = $$dmb{'data'}{'len'}{$mode}{'dl'};
1137              
1138 3 50       9 if ($$self{'data'}{'length'} eq 'unknown') {
1139 3         5 my($y,$m,$w,$d,$h,$mn,$s) = @{ $$self{'data'}{'delta'} };
  3         10  
1140              
1141 168     168   92545 no integer;
  168         459  
  168         1003  
1142 3         11 $$self{'data'}{'length'} = int($y*$yl + $m*$ml + $w*$wl +
1143             $d*$dl + $h*3600 + $mn*60 + $s);
1144             }
1145              
1146 3 50       38 if ($$delta{'data'}{'length'} eq 'unknown') {
1147 3         5 my($y,$m,$w,$d,$h,$mn,$s) = @{ $$delta{'data'}{'delta'} };
  3         8  
1148              
1149 168     168   19590 no integer;
  168         496  
  168         797  
1150 3         9 $$delta{'data'}{'length'} = int($y*$yl + $m*$ml + $w*$wl +
1151             $d*$dl + $h*3600 + $mn*60 + $s);
1152             }
1153              
1154 3         14 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: