File Coverage

blib/lib/Data/Money.pm
Criterion Covered Total %
statement 157 170 92.3
branch 61 72 84.7
condition 11 27 40.7
subroutine 38 41 92.6
pod 14 18 77.7
total 281 328 85.6


line stmt bran cond sub pod time code
1             package Data::Money;
2              
3             $Data::Money::VERSION = '0.21';
4             $Data::Money::AUTHORITY = 'cpan:GPHAT';
5              
6             =head1 NAME
7              
8             Data::Money - Money/currency with formatting and overloading.
9              
10             =head1 VERSION
11              
12             Version 0.21
13              
14             =cut
15              
16 8     8   910932 use utf8;
  8         1901  
  8         61  
17 8     8   537 use 5.006;
  8         70  
18 8     8   5210 use Moo;
  8         80606  
  8         78  
19 8     8   19840 use namespace::clean;
  8         165210  
  8         74  
20              
21 8     8   8441 use Data::Dumper;
  8         88423  
  8         796  
22 8     8   15027 use Math::BigFloat;
  8         888889  
  8         47  
23 8     8   294855 use Data::Money::BaseException::MismatchCurrencyType;
  8         51  
  8         448  
24 8     8   5290 use Data::Money::BaseException::ExcessivePrecision;
  8         37  
  8         356  
25 8     8   4678 use Data::Money::BaseException::InvalidCurrencyCode;
  8         31  
  8         365  
26 8     8   4581 use Data::Money::BaseException::InvalidCurrencyFormat;
  8         32  
  8         442  
27 8     8   5393 use Locale::Currency::Format qw/:default/;
  8         55298  
  8         2209  
28 8     8   8410 use Locale::Currency qw(code2currency);
  8         191959  
  8         2687  
29              
30             use overload
31             '+' => \&add,
32             '-' => \&subtract,
33             '*' => \&multiply,
34             '/' => \÷,
35             '%' => \&modulo,
36             '+=' => \&add_in_place,
37             '-=' => \&subtract_in_place,
38             '*=' => \&multiply_in_place,
39             '/=' => \÷_in_place,
40 0     0   0 '0+' => sub { $_[0]->value->numify; },
41 1     1   491 '""' => sub { shift->stringify },
42 13     13   808 'bool' => sub { shift->as_int; },
43             '<=>' => \&three_way_compare,
44             'cmp' => \&three_way_compare,
45 2     2   1553 'abs' => sub { shift->absolute },
46 0     0   0 '=' => sub { shift->clone },
47 8         278 'neg' => \&negate,
48 8     8   83 fallback => 1;
  8         15  
49              
50             my $Amount = sub {
51             my ($arg) = @_;
52              
53             return Math::BigFloat->new(0) unless defined $arg;
54              
55             return Math::BigFloat->new($arg->value)
56             if (ref($arg) eq 'Data::Money');
57              
58             $arg =~ tr/-()0-9.//cd;
59             if ($arg) {
60             Math::BigFloat->new($arg);
61             } else {
62             Math::BigFloat->new(0);
63             }
64             };
65              
66             my $CurrencyCode = sub {
67             my ($arg) = @_;
68              
69             Data::Money::BaseException::InvalidCurrencyCode->throw
70             unless (defined $arg
71             || ($arg =~ /^[A-Z]{3}$/mxs && defined code2currency($arg)));
72             };
73              
74             my $Format = sub {
75             my ($arg) = @_;
76              
77             my $format = {
78             'FMT_COMMON' => 1,
79             'FMT_HTML' => 1,
80             'FMT_NAME' => 1,
81             'FMT_STANDARD' => 1,
82             'FMT_SYMBOL' => 1
83             };
84              
85             Data::Money::BaseException::InvalidCurrencyFormat->throw
86             unless (defined $arg || exists $format->{uc($arg)});
87             };
88              
89             has code => (is => 'rw', isa => $CurrencyCode, default => sub { 'USD' });
90             has format => (is => 'rw', isa => $Format, default => sub { 'FMT_COMMON' });
91             has value => (is => 'rw', isa => $Amount, default => sub { Math::BigFloat->new(0) }, coerce => $Amount);
92              
93             =head1 DESCRIPTION
94              
95             The C module provides basic currency formatting and number handling
96             via L:
97              
98             my $currency = Data::Money->new(value => 1.23);
99              
100             Each C object will stringify to the original value except in string
101             context, where it stringifies to the format specified in C.
102              
103             =head1 MOTIVATION
104              
105             Data::Money was created to make it easy to use different currencies (leveraging
106             existing work in C and L), to allow math operations
107             with proper rounding (via L) and formatting via L.
108              
109             =head1 SYNOPSIS
110              
111             use strict; use warnings;
112             use Data::Money;
113              
114             my $price = Data::Money->new(value => 1.2, code => 'USD');
115             print $price; # $1.20
116             print $price->code; # USD
117             print $price->format; # FMT_COMMON
118             print $price->as_string; # $1.20
119              
120             # Overloading, returns new instance
121             my $m2 = $price + 1;
122             my $m3 = $price - 1;
123             my $m4 = $price * 1;
124             my $m5 = $price / 1;
125             my $m6 = $price % 1;
126              
127             # Objects work too
128             my $m7 = $m2 + $m3;
129             my $m8 = $m2 - $m3;
130             my $m9 = $m2 * $m3;
131             my $m10 = $m2 / $m3;
132              
133             # Modifies in place
134             $price += 1;
135             $price -= 1;
136             $price *= 1;
137             $price /= 1;
138              
139             # Compares against numbers
140             print "Currency > 2 \n" if ($m2 > 2);
141             print "Currency < 3 \n" if ($m2 < 3);
142             print "Currency == 2.2 \n" if ($m2 == 2.2);
143              
144             # And strings
145             print "Currency gt \$2.00 \n" if ($m2 gt '$2.00');
146             print "Currency lt \$3.00 \n" if ($m2 lt '$3.00');
147             print "Currency eq \$2.20 \n" if ($m2 eq '$2.20');
148              
149             # and objects
150             print "Currency m2 > m3 \n" if ($m2 > $m3);
151             print "Currency m3 lt m2 \n" if ($m3 lt $m2);
152              
153             print $price->as_string('FMT_SYMBOL'); # $1.20
154              
155             =cut
156              
157             sub BUILD {
158 196     196 0 55513 my ($self) = @_;
159              
160 196         418 my $exp = 0;
161 196         4613 my $dec = $self->value->copy->bmod(1);
162 196 100       123490 $exp = $dec->exponent->babs if ($dec);
163 196         27395 my $prec = Math::BigInt->new($self->_decimal_precision);
164              
165 195 100       24645 Data::Money::BaseException::ExcessivePrecision->throw if ($exp > $prec);
166             }
167              
168             =head1 METHODS
169              
170             =head2 name()
171              
172             Returns C object currency name.
173              
174             =head2 code($currency_code)
175              
176             Gets/sets the three letter currency code for the current currency object.Defaults
177             to USD.
178              
179             =head2 value()
180              
181             Returns the amount. Defaults to 0.
182              
183             =head2 format($string)
184              
185             Gets/sets the format to be used when C is called. See L
186             for the available formatting options. Defaults to C.
187              
188             =cut
189              
190             sub name {
191 0     0 1 0 my ($self) = @_;
192              
193 0         0 my $name = code2currency($self->code);
194             ## Fix for older Locale::Currency w/mispelled Candian
195 0         0 $name =~ s/Candian/Canadian/ms;
196              
197 0         0 return $name;
198             }
199              
200             =head2 clone(%params)
201              
202             Returns a clone (new instance) of this C object. You may optionally
203             specify some of the attributes to overwrite.
204              
205             $currency->clone({ value => 100 }); # Clones all fields but changes value to 100
206              
207             =cut
208              
209             sub clone {
210 127     127 1 19288 my ($self, %param) = @_;
211              
212 127 50 33     4481 $param{code} = $self->code unless (exists $param{code} && defined $param{code});
213 127 50 33     3708 $param{format} = $self->format unless (exists $param{format} && defined $param{format});
214 127 50 33     1362 $param{value} = $self->value unless (exists $param{value} && defined $param{value});
215 127         2790 return $self->new(\%param);
216             }
217              
218             =head2 as_float()
219              
220             Returns C object value without any formatting.
221              
222             =cut
223              
224             # Liberally jacked from Math::Currency
225             sub as_float {
226 33     33 1 3287 my ($self) = @_;
227              
228 33         1203 return $self->value->copy->bfround(0 - $self->_decimal_precision)->bstr;
229             }
230              
231             =head2 as_int()
232              
233             Returns the object's value "in pennies" (in the US at least). It strips the value
234             of formatting using C and of any decimals.
235              
236             =cut
237              
238             # Liberally jacked from Math::Currency
239             sub as_int {
240 13     13 1 33 my ($self) = @_;
241              
242 13         43 (my $str = $self->as_float) =~ s/\.//omsx;
243 13         3134 $str =~ s/^(\-?)0+/$1/omsx;
244 13 100       105 return $str eq '' ? '0' : $str;
245             }
246              
247             =head2 absolute()
248              
249             Returns a new C object with the value set to the absolute value of
250             the original.
251              
252             =cut
253              
254             sub absolute {
255 24     24 1 2232 my ($self) = @_;
256              
257 24         858 return $self->clone(value => abs $self->value);
258             }
259              
260             =head2 negate()
261              
262             Performs the negation operation, returning a new C object with the
263             opposite value (1 to -1, -2 to 2, etc).
264              
265             =cut
266              
267             sub negate {
268 7     7 1 4878 my ($self) = @_;
269              
270 7 100       258 return $self->absolute if ($self->value < 0);
271              
272 3         1824 my $val = 0 - $self->value;
273 3         1834 return $self->clone(value => $val);
274             }
275              
276             =head2 add($num)
277              
278             Adds the specified amount to this C object and returns a new C
279             object. You can supply either a number or a C object. Note that this B
280             modify the existing object.
281              
282             =cut
283              
284             sub add {
285 9     9 1 2627 my $self = shift;
286 9   50     39 my $num = shift || 0;
287              
288 9 100       42 if (ref($num) eq ref($self)) {
289 3 100       100 Data::Money::BaseException::MismatchCurrencyType->throw
290             if ($self->code ne $num->code);
291              
292 1         34 return $self->clone(value => $self->value->copy->badd($num->value));
293             }
294              
295 6         235 return $self->clone(value => $self->value->copy->badd($self->clone(value => $num)->value))
296             }
297              
298             =head2 add_in_place($num)
299              
300             Adds the specified amount to this C object, modifying its value. You
301             can supply either a number or a C object. Note that this B
302             modify the existing object.
303              
304             =cut
305              
306             sub add_in_place {
307 3     3 1 1579 my ($self, $num) = @_;
308              
309 3 100       14 if (ref($num) eq ref($self)) {
310 2 100       73 Data::Money::BaseException::MismatchCurrencyType->throw
311             if ($self->code ne $num->code);
312              
313 1         37 $self->value($self->value->copy->badd($num->value));
314             } else {
315 1         34 $self->value($self->value->copy->badd($self->clone(value => $num)->value));
316             }
317              
318 2         316 return $self;
319             }
320              
321             =head2 as_string()
322              
323             Returns C object as string.There is an alias C as well.
324              
325             =cut
326              
327             *as_string = \&stringify;
328             sub stringify {
329 18     18 0 2043 my $self = shift;
330 18   33     653 my $format = shift || $self->format;
331              
332             ## funky eval to get string versions of constants back into the values
333 18         1564 eval '$format = Locale::Currency::Format::' . $format;
334              
335 18         531 my $code = $self->code;
336 18 50 0     154 Data::Money::BaseException::InvalidCurrencyCode->throw(
337             {
338             error => 'Invalid currency code: ' . ($code || 'undef')
339             })
340             unless (_is_CurrencyCode($code));
341              
342 18         1354 my $utf8 = _to_utf8(
343             currency_format($code, $self->absolute->as_float, $format)
344             );
345              
346 18 50       809 if ($self->value < 0) {
347 0         0 return "-$utf8";
348             } else {
349 18         13224 return $utf8;
350             }
351             }
352              
353             =head2 substract($num)
354              
355             Subtracts the specified amount to this C object and returns a new
356             C object. You can supply either a number or a C object.
357             Note that this B modify the existing object.
358              
359             =cut
360              
361             sub subtract {
362 9     9 0 2584 my ($self, $num, $swap) = @_;
363 9   50     38 $num //= 0;
364              
365 9 100       38 if (ref($num) eq ref($self)) {
366 4 100       151 Data::Money::BaseException::MismatchCurrencyType->throw
367             if ($self->code ne $num->code);
368              
369 3         106 return $self->clone(value => $self->value->copy->bsub($num->value));
370             }
371              
372 5         208 my $result = $self->clone(value => $self->value->copy->bsub($self->clone(value => $num)->value));
373 5 100       415 $result = -$result if $swap;
374 5         251 return $result;
375             }
376              
377             =head2 substract_in_place($num)
378              
379             Subtracts the specified amount to this C object,modifying its value.
380             You can supply either a number or a C object. Note that this B
381             modify the existing object.
382              
383             =cut
384              
385             sub subtract_in_place {
386 3     3 0 594 my ($self, $num) = @_;
387              
388 3 100       17 if (ref($num) eq ref($self)) {
389 2 100       88 Data::Money::BaseException::MismatchCurrencyType->throw
390             if ($self->code ne $num->code);
391              
392 1         56 $self->value($self->value->copy->bsub($num->value));
393             } else {
394 1         32 $self->value($self->value->copy->bsub($self->clone(value => $num)->value));
395             }
396              
397 2         744 return $self;
398             }
399              
400             =head2 multiply($num)
401              
402             Multiplies the value of this C object and returns a new C
403             object. You dcan dsupply either a number or a C object. Note that this
404             B modify the existing object.
405              
406             =cut
407              
408             sub multiply {
409 5     5 1 670 my ($self, $num) = @_;
410              
411 5 100       24 if (ref($num) eq ref($self)) {
412 3 100       119 Data::Money::BaseException::MismatchCurrencyType->throw
413             if ($self->code ne $num->code);
414              
415 2         76 return $self->clone(value => $self->value->copy->bmul($num->value));
416             }
417              
418 2         75 return $self->clone(value => $self->value->copy->bmul($self->clone(value => $num)->value))
419             }
420              
421             =head2 multiply_in_place($num)
422              
423             Multiplies the value of this C object, modifying its value. You can
424             supply either a number or a C object. Note that this B modify
425             the existing object.
426              
427             =cut
428              
429             sub multiply_in_place {
430 5     5 1 3809 my ($self, $num) = @_;
431              
432 5 100       23 if (ref($num) eq ref($self)) {
433 3 100       112 Data::Money::BaseException::MismatchCurrencyType->throw
434             if ($self->code ne $num->code);
435              
436 2         79 $self->value($self->value->copy->bmul($num->value));
437             } else {
438 2         76 $self->value($self->value->copy->bmul($self->clone(value => $num)->value));
439             }
440              
441 4         1559 return $self;
442             }
443              
444             =head2 divide($num)
445              
446             Divides the value of this C object and returns a new C
447             object. You can supply either a number or a C object. Note that this
448             B modify the existing object.
449              
450             =cut
451              
452             sub divide {
453 5     5 1 640 my ($self, $num) = @_;
454              
455 5 100       27 if (ref($num) eq ref($self)) {
456 3 100       108 Data::Money::BaseException::MismatchCurrencyType->throw
457             if ($self->code ne $num->code);
458              
459 2         103 my $val = $self->value->copy->bdiv($num->value);
460 2         1279 return $self->clone(value => $self->_round_up($val));
461             }
462              
463 2         72 my $val = $self->value->copy->bdiv($self->clone(value => $num)->value);
464 2         1906 return $self->clone(value => $self->_round_up($val));
465             }
466              
467             =head2 divide_in_place($num)
468              
469             Divides the value of this C object, modifying its value. You can
470             supply either a number or a C object. Note that this B modify
471             the existing object.
472              
473             =cut
474              
475             sub divide_in_place {
476 5     5 1 2493 my ($self, $num) = @_;
477              
478 5         12 my $val;
479 5 100       20 if (ref($num) eq ref($self)) {
480 3 100       106 Data::Money::BaseException::MismatchCurrencyType->throw
481             if ($self->code ne $num->code);
482              
483 2         77 $val = $self->value->copy->bdiv($num->value);
484             } else {
485 2         71 $val = $self->value->copy->bdiv($self->clone(value => $num));
486             }
487              
488 4         3699 $self->value($self->_round_up($val));
489              
490 4         1023 return $self;
491             }
492              
493             =head2 modulo($num)
494              
495             Performs the modulo operation on this C object, returning a new C
496             object with the value of the remainder.
497              
498             =cut
499              
500             sub modulo {
501 1     1 1 1794 my ($self, $num) = @_;
502              
503 1 50       7 if (ref($num) eq ref($self)) {
504 1 50       38 Data::Money::BaseException::MismatchCurrencyType->throw
505             if ($self->code ne $num->code);
506              
507 0         0 my $val = $self->value->copy->bmod($num->value);
508 0         0 return $self->clone(value => $val);
509             }
510              
511 0         0 my $val = $self->value->copy->bmod($self->clone(value => $num)->value);
512 0         0 return $self->clone(value => $val);
513             }
514              
515             =head2 three_way_compare($num)
516              
517             Compares a C object to another C object, or anything it
518             is capable of coercing - numbers, numerical strings, or L objects.
519             Both numerical and string comparators work.
520              
521             =cut
522              
523             sub three_way_compare {
524 72     72 1 42625 my ($self, $num, $swap) = @_;
525 72   50     216 $num //= 0;
526              
527 72         125 my $other;
528 72 100       212 if (ref($num) eq ref($self)) {
529 16         29 $other = $num;
530             } else {
531             # we clone here to ensure that if we're comparing a number to
532             # an object, that the currency codes match (and we don't just
533             # get the default).
534 56         200 $other = $self->clone(value => $num);
535             }
536              
537 72 100       6938 Data::Money::BaseException::MismatchCurrencyType->throw(
538             {
539             error => 'Unable to compare different currency types.'
540             })
541             if ($self->code ne $other->code);
542              
543 68 100       2205 return $swap
544             ? $other->value->copy->bfround( 0 - $self->_decimal_precision )
545             <=> $self->value->copy->bfround( 0 - $self->_decimal_precision )
546             : $self->value->copy->bfround( 0 - $self->_decimal_precision )
547             <=> $other->value->copy->bfround( 0 - $self->_decimal_precision );
548             }
549              
550             #
551             #
552             # PRIVATE METHODS
553              
554             sub _decimal_precision {
555 365     365   23508 my ($self, $code) = @_;
556              
557 365   33     11611 $code ||= $self->code;
558              
559 365         3223 my $format;
560             ## funky eval to get string versions of constants back into the values
561 365         7918 eval '$format = Locale::Currency::Format::' . $self->format;
562              
563 365 100 50     2216 Data::Money::BaseException::InvalidCurrencyCode->throw(
564             {
565             error => 'Invalid currency code: ' . ($code || 'undef')
566             })
567             unless (_is_CurrencyCode($code));
568              
569 364   100     26199 return Locale::Currency::Format::decimal_precision($code) || 0;
570             }
571              
572             sub _round_up {
573 8     8   20 my ($self, $val) = @_;
574              
575 8         221 my $prec = Locale::Currency::Format::decimal_precision($self->code);
576 8         160 return sprintf('%.0'.$prec.'f', _round($val, $prec*-1));
577             }
578              
579             sub _to_utf8 {
580 18     18   6117 my $value = shift;
581              
582 18 50       61 if ($] >= 5.008) {
583 18         59 utf8::decode($value);
584             };
585              
586 18         45 return $value;
587             };
588              
589             sub _is_CurrencyCode {
590 383     383   992 my ($code) = @_;
591              
592 383 50       1138 return 0 unless defined $code;
593              
594 383         1482 return defined code2currency($code, 'alpha');
595             }
596              
597             # http://www.perlmonks.org/?node_id=24335
598             sub _round {
599 8     8   15 my ($number, $places) = @_;
600              
601 8 50       30 my $sign = ($number < 0) ? '-' : '';
602 8         3341 my $abs = abs($number);
603              
604 8 50       797 if ($places < 0) {
605 8         15 $places *= -1;
606 8         64 return $sign . substr($abs+("0." . "0" x $places . "5"), 0, $places+length(int($abs))+1);
607             } else {
608 0           my $p10 = 10**$places;
609 0           return $sign . int($abs/$p10 + 0.5)*$p10;
610             }
611             }
612              
613             =head1 OPERATOR OVERLOADING
614              
615             C overrides some operators. It is important to note which operators
616             change the object's value and which return new ones.All operators accept either a
617             C argument / a normal number via scalar and will die if the currency
618             types mismatch.
619              
620             C overloads the following operators:
621              
622             =over 4
623              
624             =item +
625              
626             Handled by the C method. Returns a new C object.
627              
628             =item -
629              
630             Handled by the C method. Returns a new C object.
631              
632             =item S< >*
633              
634             Handled by the C method. Returns a new C object.
635              
636             =item /
637              
638             Handled by the C method. Returns a new C object.
639              
640             =item +=
641              
642             Handled by the C method. Modifies the left-hand object's value.
643             Works with either a C argument or a normal number.
644              
645             =item -=
646              
647             Handled by the C method. Modifies the left-hand object's
648             value. Works with either a C argument or a normal number.
649              
650             =item *=
651              
652             Handled by the C method. Modifies the left-hand object's
653             value. Works with either a C argument or a normal number.
654              
655             =item /=
656              
657             Handled by the C method. Modifies the left-hand object's
658             value. Works with either a C argument or a normal number.
659              
660             =item <=>
661              
662             Performs a three way comparsion. Works with either a Data::Money argument or a
663             normal number.
664              
665             =back
666              
667             =head1 SEE ALSO
668              
669             =over 4
670              
671             =item L
672              
673             =item L
674              
675             =back
676              
677             =head1 ACKNOWLEDGEMENTS
678              
679             This module was originally based on L by Christopher H. Laco but I
680             I opted to fork and create a whole new module because my work was wildly different
681             from the original. I decided it was better to make a new module than to break back
682             compat and surprise users. Many thanks to him for the great module.
683              
684             Inspiration and ideas were also drawn from L and L.
685              
686             Major contributions (more overloaded operators, disallowing operations on mismatched
687             currences, absolute value, negation and unit tests) from Andrew Nelson C<< >>.
688              
689             =head1 AUTHOR
690              
691             Cory G Watson, C<< >>
692              
693             Currently maintained by Mohammad S Anwar (MANWAR) C<< >>
694              
695             =head1 REPOSITORY
696              
697             L
698              
699             =head1 LICENSE AND COPYRIGHT
700              
701             Copyright 2010 Cory Watson
702              
703             This program is free software; you can redistribute it and/or modify it under the
704             terms of either: the GNU General Public License as published by the Free Software
705             Foundation; or the Artistic License.
706              
707             See L for more information.
708              
709             =cut
710              
711             1; # End of Data::Money