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   1141 use Date::Manip::Obj;
  168         299  
  168         7663  
15             @ISA = ('Date::Manip::Obj');
16              
17             require 5.010000;
18 168     168   881 use warnings;
  168         327  
  168         4602  
19 168     168   765 use strict;
  168         295  
  168         3930  
20 168     168   823 use utf8;
  168         283  
  168         1135  
21 168     168   4131 use IO::File;
  168         334  
  168         26400  
22 168     168   1030 use Carp;
  168         284  
  168         8795  
23             #use re 'debug';
24              
25 168     168   930 use Date::Manip::Base;
  168         287  
  168         4018  
26 168     168   818 use Date::Manip::TZ;
  168         340  
  168         232016  
27              
28             our $VERSION;
29             $VERSION='6.92';
30 168     168   846 END { undef $VERSION; }
31              
32             ########################################################################
33             # BASE METHODS
34             ########################################################################
35              
36             sub is_delta {
37 1     1 1 160 return 1;
38             }
39              
40             sub config {
41 15     15 1 106 my($self,@args) = @_;
42 15         137 $self->SUPER::config(@args);
43              
44             # A new config can change the value of the format fields, so clear them.
45 15         53 $$self{'data'}{'f'} = {};
46 15         64 $$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   17780 my($self) = @_;
54              
55 12026         22551 my $def = [0,0,0,0,0,0,0];
56 12026         17402 my $dmt = $$self{'tz'};
57 12026         15001 my $dmb = $$dmt{'base'};
58              
59 12026         16013 $$self{'err'} = '';
60 12026         81388 $$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         4 my @args = @{ $$self{'args'} };
  2         6  
85 2         11 $self->parse(@args);
86             }
87              
88             sub value {
89 3422     3422 1 6522 my($self,$as_input) = @_;
90              
91 3422 50       6450 if ($$self{'err'}) {
92 0 0       0 return () if (wantarray);
93 0         0 return '';
94             }
95              
96 3422         4490 my $dmt = $$self{'tz'};
97 3422         4295 my $dmb = $$dmt{'base'};
98              
99 3422         3975 my @delta = @{ $$self{'data'}{'delta'} };
  3422         7994  
100              
101 3422 100       11349 return @delta if (wantarray);
102 339         394 my $err;
103              
104             my %o = ( 'source' => 'delta',
105             'nonorm' => 1,
106             'type' => $$self{'data'}{'type'},
107             'sign' => 0,
108 339         1104 'mode' => $$self{'data'}{'mode'},
109             );
110              
111 339         985 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
112 339         1994 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   598788 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 126513 my($self,@args) = @_;
129 4988         6157 my %opts;
130 4988 100       8939 if (ref($args[0]) eq 'HASH') {
131 120         134 %opts = %{ $args[0] };
  120         454  
132             } else {
133             # *** DEPRECATED 7.0 ***
134 4868 100       8169 if (@args == 3) {
135 15 50       43 %opts = ( $args[0] => $args[1],
136             'nonorm' => ($args[2] ? 1 : 0) );
137             } else {
138 4853         9128 %opts = ( $args[0] => $args[1] );
139             }
140             }
141              
142             # Check for some invalid opts
143              
144 4988         11763 foreach my $key (keys %opts) {
145 5163         7274 my $val = $opts{$key};
146 5163         7587 delete $opts{$key};
147              
148             # *** DEPRECATED 7.0 ***
149 5163 50       9846 $key = 'standard' if (lc($key) eq 'normal');
150              
151 5163 100 100     14237 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       9932 if (exists $opts{lc($key)}) {
159 1         2 $key = lc($key);
160 1         6 $$self{'err'} = "[set] Invalid option: $key entered twice";
161 1         5 return 1;
162             }
163              
164 5152         10261 $opts{lc($key)} = $val;
165              
166             } elsif ($key =~ /^[yMwdhms]$/) {
167              
168 9         24 $opts{$key} = $val;
169              
170             } else {
171 1         4 $$self{'err'} = "[set] Unknown option: $key";
172 1         3 return 1;
173             }
174             }
175              
176 4986 100 66     47109 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         5 $$self{'err'} = "[set] Fields set multiple times";
184 2         5 return 1;
185             }
186              
187 4984 100 100     10700 if (exists $opts{'mode'} && $opts{'mode'} !~ /^(business|standard)$/) {
188 1         3 $$self{'err'} = "[set] Unknown value for mode: $opts{mode}";
189 1         3 return 1;
190             }
191 4983 100 100     9510 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         3 return 1;
195             }
196              
197 4982 100       13562 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         3 return 1;
203             } elsif (exists $opts{'business'}) {
204 166         254 $opts{'delta'} = $opts{'business'};
205 166         269 $opts{'mode'} = 'business';
206             } elsif (exists $opts{'standard'}) {
207 8         18 $opts{'delta'} = $opts{'standard'};
208 8         39 $opts{'mode'} = 'standard';
209             }
210              
211             # If we are setting delta/business/standard, we need to initialize
212             # all the parameters.
213              
214 4981         5856 my @delta;
215 4981 100       7541 if (exists $opts{'delta'}) {
216 4938 100       10027 if (ref($opts{'delta'}) ne 'ARRAY') {
217 1         4 $$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         10205 $self->_init();
223 4937         7616 @delta = @{ $opts{'delta'} };
  4937         10123  
224              
225             } else {
226 43         53 @delta = @{ $$self{'data'}{'delta'} };
  43         104  
227             }
228              
229             # Figure out the parameters. Include the nonorm/mode/type
230             # options.
231              
232 4980         5961 my $err;
233 4980         6251 my $dmt = $$self{'tz'};
234 4980         5976 my $dmb = $$dmt{'base'};
235 4980 100       8639 my $gotmode = (exists $opts{'mode'} ? 1 : $$self{'data'}{'gotmode'});
236             my $mode = (exists $opts{'mode'} ? $opts{'mode'} :
237 4980 100       9016 $$self{'data'}{'mode'});
238 4980 100       7530 my $nonorm = (exists $opts{'nonorm'} ? $opts{'nonorm'} : 0);
239              
240 4980         6391 my ($type,$type_from);
241 4980 100       7146 if (exists $opts{'type'}) {
242 94         156 $type = $opts{'type'};
243 94         140 $type_from = 'opt';
244             } else {
245 4886         6666 $type = $$self{'data'}{'type'};
246 4886         6333 $type_from = $$self{'data'}{'type_from'};
247             }
248              
249             # If we're setting individual fields, do that now
250              
251             {
252 4980         5816 my $field_set = 0;
  4980         5561  
253              
254             # Check all individual fields
255 4980         7842 foreach my $opt (qw(y M w d h m s)) {
256 34848 100       50358 if (exists $opts{$opt}) {
257 8 100       19 if (ref($opts{$opt})) {
258 1         4 $$self{'err'} = "[set] Option $opt requires a scalar value";
259 1         4 return 1;
260             }
261 7         12 my $val = $opts{$opt};
262 7 100       22 if (! $dmb->_is_num($val)) {
263 1         5 $$self{'err'} = "[set] Option $opt requires a numerical value";
264 1         6 return 1;
265             }
266 6         13 $delta[ $f{$opt} ] = $val;
267 6         10 $field_set = 1;
268             }
269             }
270              
271             # If none were set, than we're done with setting.
272 4978 100       9486 last if (! $field_set);
273              
274 6 50       12 if ($$self{'err'}) {
275 0         0 return 1;
276             }
277             }
278              
279             # Check that the type is consistent with @delta.
280              
281 4978         14866 ($err,$type,$type_from) =
282             $dmb->_check_delta_type($mode,$type,$type_from,@delta);
283              
284 4978 100       9386 if ($err) {
285 11         24 $$self{'err'} = "[set] $err";
286 11         35 return 1;
287             }
288              
289 4967         16276 my %o = ( 'source' => 'delta',
290             'nonorm' => $nonorm,
291             'type' => $type,
292             'sign' => -1,
293             'mode' => $mode,
294             );
295              
296 4967         17432 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
297              
298 4967 100       11486 if ($err) {
299 2         6 $$self{'err'} = "[set] $err";
300 2         8 return 1;
301             }
302              
303 4965         13530 $$self{'data'}{'delta'} = [ @delta ];
304 4965         7420 $$self{'data'}{'mode'} = $mode;
305 4965         6313 $$self{'data'}{'gotmode'} = $gotmode;
306 4965         6510 $$self{'data'}{'type'} = $type;
307 4965         6498 $$self{'data'}{'type_from'} = $type_from;
308 4965         6940 $$self{'data'}{'normalized'} = 1-$nonorm;
309 4965         6849 $$self{'data'}{'length'} = 'unknown';
310 4965         6633 $$self{'data'}{'in'} = '';
311              
312 4965         17260 return 0;
313             }
314             }
315              
316             sub _rx {
317 2101     2101   2864 my($self,$rx) = @_;
318 2101         2536 my $dmt = $$self{'tz'};
319 2101         2283 my $dmb = $$dmt{'base'};
320              
321             return $$dmb{'data'}{'rx'}{'delta'}{$rx}
322 2101 100       4826 if (exists $$dmb{'data'}{'rx'}{'delta'}{$rx});
323              
324 84 100       435 if ($rx eq 'expanded') {
    100          
    50          
325 26         57 my $sign = '[-+]?\s*';
326 26         52 my $sep = '(?:,\s*|\s+|$)';
327              
328 26         83 my $nth = $$dmb{'data'}{'rx'}{'nth'}[0];
329 26         75 my $yf = $$dmb{data}{rx}{fields}[1];
330 26         63 my $mf = $$dmb{data}{rx}{fields}[2];
331 26         69 my $wf = $$dmb{data}{rx}{fields}[3];
332 26         56 my $df = $$dmb{data}{rx}{fields}[4];
333 26         62 my $hf = $$dmb{data}{rx}{fields}[5];
334 26         57 my $mnf = $$dmb{data}{rx}{fields}[6];
335 26         62 my $sf = $$dmb{data}{rx}{fields}[7];
336 26         48 my $num = '(?:\d+(?:\.\d*)?|\.\d+)';
337              
338 26         196 my $y = "(?:(?:(?<y>$sign$num)|(?<y>$nth))\\s*(?:$yf)$sep)";
339 26         170 my $m = "(?:(?:(?<m>$sign$num)|(?<m>$nth))\\s*(?:$mf)$sep)";
340 26         152 my $w = "(?:(?:(?<w>$sign$num)|(?<w>$nth))\\s*(?:$wf)$sep)";
341 26         151 my $d = "(?:(?:(?<d>$sign$num)|(?<d>$nth))\\s*(?:$df)$sep)";
342 26         155 my $h = "(?:(?:(?<h>$sign$num)|(?<h>$nth))\\s*(?:$hf)$sep)";
343 26         198 my $mn = "(?:(?:(?<mn>$sign$num)|(?<mn>$nth))\\s*(?:$mnf)$sep)";
344 26         193 my $s = "(?:(?:(?<s>$sign$num)|(?<s>$nth))\\s*(?:$sf)?)";
345              
346 26         117841 my $exprx = qr/^\s*$y?$m?$w?$d?$h?$mn?$s?\s*$/i;
347 26         1632 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $exprx;
348              
349             } elsif ($rx eq 'mode') {
350              
351 32         692 my $mode = qr/\b($$dmb{'data'}{'rx'}{'mode'}[0])\b/i;
352 32         130 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $mode;
353              
354             } elsif ($rx eq 'when') {
355              
356 26         1224 my $when = qr/\b($$dmb{'data'}{'rx'}{'when'}[0])\b/i;
357 26         114 $$dmb{'data'}{'rx'}{'delta'}{$rx} = $when;
358              
359             }
360              
361 84         241 return $$dmb{'data'}{'rx'}{'delta'}{$rx};
362             }
363              
364             sub parse {
365 773     773 1 175477 my($self,$instring,@args) = @_;
366 773         1697 $self->_init();
367              
368 773         1152 my %opts;
369 773 50       1630 if (ref($args[0]) eq 'HASH') {
370 0         0 %opts = %{ $args[0] };
  0         0  
371              
372             } else {
373             # *** DEPRECATED 7.0 ***
374              
375 773         993 my($business,$no_normalize);
376              
377 773 50       2374 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       6 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         1258 my $dmt = $$self{'tz'};
408 773         961 my $dmb = $$dmt{'base'};
409 773         1470 $self->_init();
410              
411 773 50       1529 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         1000 my ($err,@delta,$mode);
424 773         1000 $mode = '';
425 773         970 $$self{'err'} = '';
426 773         2666 $instring =~ s/^\s*//;
427 773         3505 $instring =~ s/\s*$//;
428              
429             PARSE: {
430              
431             # First, we'll try the standard format (without a mode string)
432              
433 773         1168 ($err,@delta) = $dmb->_split_delta($instring);
  773         2133  
434 773 100       1683 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         1276 my @strings = $dmb->_encoding($instring);
441 513         1149 my $moderx = $self->_rx('mode');
442              
443 513         867 foreach my $string (@strings) {
444 1010 100       8010 if ($string =~ s/\s*$moderx\s*//i) {
445 174         402 my $m = $1;
446 174 100       562 if ($$dmb{'data'}{'wordmatch'}{'mode'}{lc($m)} == 1) {
447 1         2 $m = 'standard';
448             } else {
449 173         233 $m = 'business';
450             }
451 174         208 $mode = $m;
452              
453 174         350 ($err,@delta) = $dmb->_split_delta($string);
454 174 100       496 last PARSE if (! $err);
455             }
456             }
457              
458             # Now we'll check each string for an expanded form delta.
459              
460 424         669 foreach my $string (@strings) {
461 794         1036 my $past = 0;
462              
463 794         1256 my $whenrx = $self->_rx('when');
464 794 100 66     5089 if ($string &&
465             $string =~ s/$whenrx//i) {
466 50         119 my $when = $1;
467 50 100       182 if ($$dmb{'data'}{'wordmatch'}{'when'}{lc($when)} == 1) {
468 16         41 $past = 1;
469             }
470             }
471              
472 794         1379 my $rx = $self->_rx('expanded');
473 794 100 66     12889 if ($string &&
474             $string =~ $rx) {
475 114         1442 @delta = @+{qw(y m w d h mn s)};
476 114         381 foreach my $f (@delta) {
477 798 100       1316 if (! defined $f) {
    100          
478 660         733 $f = 0;
479             } elsif (exists $$dmb{'data'}{'wordmatch'}{'nth'}{lc($f)}) {
480 4         12 $f = $$dmb{'data'}{'wordmatch'}{'nth'}{lc($f)};
481             } else {
482 134         379 $f =~ s/\s//g;
483             }
484             }
485              
486             # if $past, reverse the signs
487 114 100       217 if ($past) {
488 12         31 foreach my $v (@delta) {
489 84         99 $v *= -1;
490             }
491             }
492              
493 114         273 last PARSE;
494             }
495             }
496             }
497              
498 773 100       1633 if (! @delta) {
499 310         516 $$self{'err'} = "[parse] Invalid delta string";
500 310         875 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     1089 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       841 $mode = $opts{'mode'} if (exists $opts{'mode'});
512 463 100       847 $mode = 'standard' if (! $mode);
513              
514             # Figure out the type.
515              
516             my %o = ( 'source' => 'string',
517 463 50       1714 'nonorm' => (exists $opts{'nonorm'} ? $opts{'nonorm'} : 0),
518             'sign' => -1,
519             'mode' => $mode,
520             );
521              
522 463         2003 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
523 463         1027 my $type = $o{'type'};
524 463         634 my $type_from = $o{'type_from'};
525              
526 463 50       794 if ($err) {
527 0         0 $$self{'err'} = "[parse] $err";
528 0         0 return 1;
529             }
530              
531 463         946 $$self{'data'}{'in'} = $instring;
532 463         1151 $$self{'data'}{'delta'} = [@delta];
533 463         745 $$self{'data'}{'mode'} = $mode;
534 463 50 33     1218 $$self{'data'}{'gotmode'} = ($mode || exists $opts{'mode'} ? 1 : 0);
535 463         637 $$self{'data'}{'type'} = $type;
536 463         581 $$self{'data'}{'type_from'} = $type_from;
537 463         662 $$self{'data'}{'length'} = 'unknown';
538 463 50       846 $$self{'data'}{'normalized'} = ($opts{'nonorm'} ? 0 : 1);
539              
540 463         1523 return 0;
541             }
542              
543             sub printf {
544 2350     2350 1 4853 my($self,@in) = @_;
545 2350 50       4954 if ($$self{'err'}) {
546 0         0 carp "WARNING: [printf] Object must contain a valid delta";
547 0         0 return undef;
548             }
549              
550 2350         2866 my($y,$M,$w,$d,$h,$m,$s) = @{ $$self{'data'}{'delta'} };
  2350         6286  
551              
552 2350         3115 my @out;
553 2350         3657 foreach my $in (@in) {
554 2352         3209 my $out = '';
555 2352         4042 while ($in) {
556 2812 100       22695 if ($in =~ s/^([^%]+)//) {
    100          
    100          
    100          
    100          
    50          
557 268         596 $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         48 my($sign,$pad,$width,$field) = ($1,$2,$3,$4);
570 18         39 $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         12035 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         6247 my @field = qw(y M w d h m s);
587 2503   66     9455 while (@field && $field[0] ne $field0) {
588 338         737 shift(@field);
589             }
590 2503   66     8147 while (@field && $field[$#field] ne $field1) {
591 342         700 pop(@field);
592             }
593              
594 2503 50       4461 if (! @field) {
595 0         0 $out .= $match;
596             } else {
597 2503         5986 $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         16 my($sign,$pad,$width) = ($1,$2,$3);
608 6         15 $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         52 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         32 my @field = qw(y M w d h m s);
623 16   66     61 while (@field && $field[0] ne $field0) {
624 7         15 shift(@field);
625             }
626 16   66     42 while (@field && $field[$#field] ne $field1) {
627 59         127 pop(@field);
628             }
629              
630 16 50       24 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         4607 push(@out,$out);
643             }
644              
645 2350 100       5674 if (wantarray) {
    50          
646 58         198 return @out;
647             } elsif (@out == 1) {
648 2292         6781 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         27 my $dmt = $$self{'tz'};
657 22         26 my $dmb = $$dmt{'base'};
658 22         25 my @delta = @{ $$self{'data'}{'delta'} };
  22         41  
659 22         27 my $delta;
660 22         75 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         30 my $s = "+";
665 22         23 foreach my $f (@delta) {
666 154 100       222 if ($f < 0) {
    100          
667 13         15 $s = "-";
668             } elsif ($f > 0) {
669 116         109 $s = "+";
670 116         110 $f *= 1;
671 116         159 $f = "+$f";
672             } else {
673 25         40 $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         24 my @set;
684 22         29 my $mode = $$self{'data'}{'mode'};
685              
686 22         25 my $f0 = $tmp{$field0};
687 22         25 my $f1 = $tmp{$field1};
688              
689 22 100       39 if ($field0 eq $field1) {
    100          
690 3         7 @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         10 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         3 push(@set, [ $delta[2] ]);
713 2 50       5 $f0 = ($f1 == 2 ? 7 : 3);
714             }
715              
716 4 100       8 if ($f0 <= 6) {
717 3         10 push(@set, [ @delta[$f0..$f1] ]);
718             }
719              
720             } else {
721              
722 15 100       25 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         35 push(@set, [ @delta[$f0..1] ]);
730 14 100       23 $f0 = ($f1 == 1 ? 7 : 2);
731             }
732              
733 15 100       26 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       17 if ($f1 <= 3) {
742 7         15 push(@set, [ @delta[$f0..$f1] ]);
743 7         8 $f0 = 7;
744             } else {
745 6         13 push(@set, [ @delta[$f0..3] ]);
746 6         7 $f0 = 4;
747             }
748             }
749              
750 15 100       35 if ($f0 <= 6) {
751 6         15 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         26 my @ret;
759              
760 22         26 foreach my $set (@set) {
761 44         75 my @f = @$set;
762              
763 44 100 66     94 if (defined($sign) && $sign eq "+") {
764 16         28 push(@ret,@f);
765             } else {
766 28         38 push(@ret,shift(@f));
767 28         36 foreach my $f (@f) {
768 26         59 $f =~ s/[-+]//;
769 26         47 push(@ret,$f);
770             }
771             }
772             }
773              
774             # Width/pad
775              
776 22         44 my $ret = join(':',@ret);
777 22 100 100     47 if ($width && length($ret) < $width) {
778 3 100 100     10 if (defined $pad && $pad eq ">") {
779 1         3 $ret .= ' 'x($width-length($ret));
780             } else {
781 2         5 $ret = ' 'x($width-length($ret)) . $ret;
782             }
783             }
784              
785 22         123 return $ret;
786             }
787              
788             sub _printf_field {
789 2521     2521   6657 my($self,$sign,$pad,$width,$precision,$field,@field) = @_;
790              
791 2521         5454 my $val = $self->_printf_field_val($field,@field);
792 2521 100       5045 $pad = "<" if (! defined($pad));
793              
794             # Strip off the sign.
795              
796 2521         3274 my $s = '';
797              
798 2521 100       5678 if ($val < 0) {
    100          
799 66         98 $s = "-";
800 66         89 $val *= -1;
801             } elsif ($sign) {
802 16         23 $s = "+";
803             }
804              
805             # Handle the precision.
806              
807 2521 100       5331 if (defined($precision)) {
    50          
808 222         886 $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       3874 if ($width) {
821 38 100       62 if ($pad eq ">") {
    100          
822 8         11 $val = "$s$val";
823 8 100       18 my $pad = ($width > length($val) ? $width - length($val) : 0);
824 8         38 $val .= ' 'x$pad;
825              
826             } elsif ($pad eq "<") {
827 15         19 $val = "$s$val";
828 15 100       29 my $pad = ($width > length($val) ? $width - length($val) : 0);
829 15         23 $val = ' 'x$pad . $val;
830              
831             } else {
832 15 100       31 my $pad = ($width > length($val)-length($s) ?
833             $width - length($val) - length($s): 0);
834 15         23 $val = $s . '0'x$pad . $val;
835             }
836             } else {
837 2483         4826 $val = "$s$val";
838             }
839              
840 2521         9167 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   5708 my($self,$field,@field) = @_;
847              
848 2521 50 66     9811 if (! exists $$self{'data'}{'f'}{'y'} &&
849             ! exists $$self{'data'}{'f'}{'y'}{'y'}) {
850              
851 1516         2231 my($yv,$Mv,$wv,$dv,$hv,$mv,$sv) = map { $_*1 } @{ $$self{'data'}{'delta'} };
  10612         14978  
  1516         3751  
852 1516         3360 $$self{'data'}{'f'}{'y'}{'y'} = $yv;
853 1516         3070 $$self{'data'}{'f'}{'M'}{'M'} = $Mv;
854 1516         2999 $$self{'data'}{'f'}{'w'}{'w'} = $wv;
855 1516         2990 $$self{'data'}{'f'}{'d'}{'d'} = $dv;
856 1516         2945 $$self{'data'}{'f'}{'h'}{'h'} = $hv;
857 1516         2829 $$self{'data'}{'f'}{'m'}{'m'} = $mv;
858 1516         3313 $$self{'data'}{'f'}{'s'}{'s'} = $sv;
859             }
860              
861             # A single field
862              
863 2521 100       4820 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       5556 if (! exists $$self{'data'}{'flen'}{'s'}) {
870 1510         2582 my $mode = $$self{'data'}{'mode'};
871 1510         4213 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         9186 'y' => $$dmb{'data'}{'len'}{$mode}{'yl'},
879             };
880             }
881              
882             # Calculate the value for each field.
883              
884 2503         3821 my $val = 0;
885 2503         3869 foreach my $f (@field) {
886              
887             # We want the value of $f expressed in terms of $field
888              
889 16841 100       28127 if (! exists $$self{'data'}{'f'}{$f}{$field}) {
890              
891             # Get the value of $f expressed in seconds
892              
893 9372 100       14197 if (! exists $$self{'data'}{'f'}{$f}{'s'}) {
894             $$self{'data'}{'f'}{$f}{'s'} =
895 9033         14818 $$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         16334 $$self{'data'}{'f'}{$f}{'s'} / $$self{'data'}{'flen'}{$field};
902             }
903              
904 16841         23508 $val += $$self{'data'}{'f'}{$f}{$field};
905             }
906              
907 2503         4768 return $val;
908             }
909              
910             sub type {
911 54     54 1 3134 my($self,$op) = @_;
912 54         79 $op = lc($op);
913              
914 54 100 100     133 if ($op eq 'business' ||
915             $op eq 'standard') {
916 33 100       93 return ($$self{'data'}{'mode'} eq $op ? 1 : 0);
917             }
918              
919 21 100       62 return ($$self{'data'}{'type'} eq $op ? 1 : 0);
920             }
921              
922             sub calc {
923 29     29 1 123 my($self,$obj,@args) = @_;
924 29 50       67 if ($$self{'err'}) {
925 0         0 $$self{'err'} = "[calc] First object invalid (delta)";
926 0         0 return undef;
927             }
928              
929 29 50       113 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       64 if ($$obj{'err'}) {
938 0         0 $$self{'err'} = "[calc] Second object invalid (delta)";
939 0         0 return undef;
940             }
941 29         79 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   64 my($type1,$type2) = @_;
951 29 100       72 return $type1 if ($type1 eq $type2);
952 2         4 foreach my $type ('estimate','approx','semi') {
953 6 100 66     18 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   45 my($self,$delta,@args) = @_;
960 29         45 my $dmt = $$self{'tz'};
961 29         44 my $dmb = $$dmt{'base'};
962 29         108 my $ret = $self->new_delta;
963              
964 29         43 my($subtract,$no_normalize);
965 29 50       112 if (@args > 2) {
966 0         0 $$ret{'err'} = "Unknown args in calc";
967 0         0 return $ret;
968             }
969              
970 29 50       86 if (@args == 2) {
    100          
971 0         0 ($subtract,$no_normalize) = @args;
972             } elsif (@args == 1) {
973 4 50       14 if ($args[0] eq 'nonormalize') {
974 0         0 $subtract = 0;
975 0         0 $no_normalize = 1;
976             } else {
977 4         8 $subtract = $args[0];
978 4         7 $no_normalize = 0;
979             }
980             } else {
981 25         40 $subtract = 0;
982 25         32 $no_normalize = 0;
983             }
984              
985 29 50       79 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         38 my ($err,@delta);
992 29         70 for (my $i=0; $i<7; $i++) {
993 203 100       241 if ($subtract) {
994 28         49 $delta[$i] = $$self{'data'}{'delta'}[$i] - $$delta{'data'}{'delta'}[$i];
995             } else {
996 175         354 $delta[$i] = $$self{'data'}{'delta'}[$i] + $$delta{'data'}{'delta'}[$i];
997             }
998             }
999              
1000             my $type = __type_max($$self{'data'}{'type'},
1001 29         74 $$delta{'data'}{'type'});
1002             my %o = ( 'source' => 'delta',
1003             'nonorm' => $no_normalize,
1004             'sign' => -1,
1005             'type' => $type,
1006 29         109 'mode' => $$self{'data'}{'mode'},
1007             );
1008              
1009 29         98 ($err,@delta) = $dmb->_delta_fields( \%o, [@delta]);
1010              
1011 29         70 $$ret{'data'}{'in'} = '';
1012 29         76 $$ret{'data'}{'delta'} = [@delta];
1013 29         51 $$ret{'data'}{'mode'} = $$self{'data'}{'mode'};
1014 29         45 $$ret{'data'}{'gotmode'} = 1;
1015 29         50 $$ret{'data'}{'type'} = $type;
1016 29         44 $$ret{'data'}{'type_from'} = 'det';
1017 29         43 $$ret{'data'}{'length'} = 'unknown';
1018 29         41 $$ret{'data'}{'normalized'} = 1-$no_normalize;
1019              
1020 29         133 return $ret;
1021             }
1022              
1023             sub convert {
1024 62     62 1 195 my($self,$to) = @_;
1025              
1026 62         178 my %mode_val = ( 'exact' => 0,
1027             'semi' => 1,
1028             'approx' => 2,
1029             'estimated' => 3,
1030             );
1031              
1032 62         85 my $from = $$self{'data'}{'type'};
1033 62         78 my $from_val = $mode_val{$from};
1034 62         77 my $to_val = $mode_val{$to};
1035              
1036 62 100       124 return if ($from_val == $to_val);
1037              
1038             #
1039             # Converting from exact to less exact
1040             #
1041              
1042 43 100       77 if ($from_val < $to_val) {
1043              
1044 35         119 $self->set( { 'nonorm' => 0,
1045             'type' => $to } );
1046 35         109 return;
1047             }
1048              
1049             #
1050             # Converting from less exact to more exact
1051             # *** DEPRECATE *** 7.00
1052             #
1053              
1054 8         9 my @fields;
1055             {
1056 168     168   1477 no integer;
  168         396  
  168         1416  
  8         9  
1057              
1058 8         29 my $dmb = $self->base();
1059 8         16 my $mode= $$self{'data'}{'mode'};
1060 8         13 my $yl = $$dmb{'data'}{'len'}{$mode}{'yl'};
1061 8         13 my $ml = $$dmb{'data'}{'len'}{$mode}{'ml'};
1062 8         15 my $wl = $$dmb{'data'}{'len'}{$mode}{'wl'};
1063 8         10 my $dl = $$dmb{'data'}{'len'}{$mode}{'dl'};
1064              
1065             # Convert it to seconds
1066              
1067 8         10 my($y,$m,$w,$d,$h,$mn,$s) = @{ $$self{'data'}{'delta'} };
  8         20  
1068 8         20 $s += $y*$yl + $m*$ml + $w*$wl + $d*$dl + $h*3600 + $mn*60;
1069              
1070 8         16 @fields = (0,0,0,0,0,0,$s);
1071              
1072 8 100       14 if ($mode eq 'business') {
1073              
1074 4 50 66     28 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         10 @fields = $dmb->_normalize_bus_exact(@fields);
1083             }
1084              
1085             } else {
1086              
1087 4 50 66     17 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         4 @fields = $dmb->_normalize_approx(@fields);
1093              
1094             } else {
1095 3         10 @fields = $dmb->_normalize_exact(@fields);
1096             }
1097              
1098             }
1099             }
1100              
1101 8         24 $$self{'data'}{'delta'} = [ @fields ];
1102 8         12 $$self{'data'}{'gotmode'} = 1;
1103 8         13 $$self{'data'}{'type'} = $to;
1104 8         13 $$self{'data'}{'type_from'} = 'opt';
1105 8         11 $$self{'data'}{'normalized'} = 1;
1106 8         21 $$self{'data'}{'length'} = 'unknown';
1107             }
1108              
1109             sub cmp {
1110 3     3 1 15 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         16 my $dmb = $self->base();
1133 3         7 my $yl = $$dmb{'data'}{'len'}{$mode}{'yl'};
1134 3         4 my $ml = $$dmb{'data'}{'len'}{$mode}{'ml'};
1135 3         5 my $wl = $$dmb{'data'}{'len'}{$mode}{'wl'};
1136 3         5 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         6  
1140              
1141 168     168   74392 no integer;
  168         357  
  168         764  
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       29 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   16251 no integer;
  168         379  
  168         700  
1150 3         6 $$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: