File Coverage

blib/lib/Data/Money.pm
Criterion Covered Total %
statement 156 170 91.7
branch 61 72 84.7
condition 11 27 40.7
subroutine 37 41 90.2
pod 14 18 77.7
total 279 328 85.0


line stmt bran cond sub pod time code
1             package Data::Money;
2              
3             $Data::Money::VERSION = '0.20';
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.20
13              
14             =cut
15              
16 8     8   412155 use 5.006;
  8         61  
17 8     8   3492 use Moo;
  8         73906  
  8         30  
18 8     8   12276 use namespace::clean;
  8         75334  
  8         40  
19              
20 8     8   5626 use Data::Dumper;
  8         42552  
  8         394  
21 8     8   7028 use Math::BigFloat;
  8         364637  
  8         32  
22 8     8   155969 use Data::Money::BaseException::MismatchCurrencyType;
  8         25  
  8         250  
23 8     8   2961 use Data::Money::BaseException::ExcessivePrecision;
  8         18  
  8         222  
24 8     8   2944 use Data::Money::BaseException::InvalidCurrencyCode;
  8         19  
  8         235  
25 8     8   2947 use Data::Money::BaseException::InvalidCurrencyFormat;
  8         17  
  8         251  
26 8     8   3811 use Locale::Currency::Format;
  8         30899  
  8         728  
27 8     8   3056 use Locale::Currency qw(code2currency);
  8         106508  
  8         1582  
28              
29             use overload
30             '+' => \&add,
31             '-' => \&subtract,
32             '*' => \&multiply,
33             '/' => \÷,
34             '%' => \&modulo,
35             '+=' => \&add_in_place,
36             '-=' => \&subtract_in_place,
37             '*=' => \&multiply_in_place,
38             '/=' => \÷_in_place,
39 0     0   0 '0+' => sub { $_[0]->value->numify; },
40 0     0   0 '""' => sub { shift->stringify },
41 33     33   462 'bool' => sub { shift->as_int; },
42             '<=>' => \&three_way_compare,
43             'cmp' => \&three_way_compare,
44 2     2   501 'abs' => sub { shift->absolute },
45 0     0   0 '=' => sub { shift->clone },
46 8         148 'neg' => \&negate,
47 8     8   54 fallback => 1;
  8         9  
48              
49             my $Amount = sub {
50             my ($arg) = @_;
51              
52             return Math::BigFloat->new(0) unless defined $arg;
53              
54             return Math::BigFloat->new($arg->value)
55             if (ref($arg) eq 'Data::Money');
56              
57             $arg =~ tr/-()0-9.//cd;
58             if ($arg) {
59             Math::BigFloat->new($arg);
60             } else {
61             Math::BigFloat->new(0);
62             }
63             };
64              
65             my $CurrencyCode = sub {
66             my ($arg) = @_;
67              
68             Data::Money::BaseException::InvalidCurrencyCode->throw
69             unless (defined $arg
70             || ($arg =~ /^[A-Z]{3}$/mxs && defined code2currency($arg)));
71             };
72              
73             my $Format = sub {
74             my ($arg) = @_;
75              
76             my $format = {
77             'FMT_COMMON' => 1,
78             'FMT_HTML' => 1,
79             'FMT_NAME' => 1,
80             'FMT_STANDARD' => 1,
81             'FMT_SYMBOL' => 1
82             };
83              
84             Data::Money::BaseException::InvalidCurrencyFormat->throw
85             unless (defined $arg || exists $format->{uc($arg)});
86             };
87              
88             has code => (is => 'rw', isa => $CurrencyCode, default => sub { 'USD' });
89             has format => (is => 'rw', isa => $Format, default => sub { 'FMT_COMMON' });
90             has value => (is => 'rw', isa => $Amount, default => sub { Math::BigFloat->new(0) }, coerce => $Amount);
91              
92             =head1 DESCRIPTION
93              
94             The C module provides basic currency formatting and number handling
95             via L:
96              
97             my $currency = Data::Money->new(value => 1.23);
98              
99             Each C object will stringify to the original value except in string
100             context, where it stringifies to the format specified in C.
101              
102             =head1 MOTIVATION
103              
104             Data::Money was created to make it easy to use different currencies (leveraging
105             existing work in C and L), to allow math operations
106             with proper rounding (via L) and formatting via L.
107              
108             =head1 SYNOPSIS
109              
110             use strict; use warnings;
111             use Data::Money;
112              
113             my $price = Data::Money->new(value => 1.2, code => 'USD');
114             print $price; # $1.20
115             print $price->code; # USD
116             print $price->format; # FMT_COMMON
117             print $price->as_string; # $1.20
118              
119             # Overloading, returns new instance
120             my $m2 = $price + 1;
121             my $m3 = $price - 1;
122             my $m4 = $price * 1;
123             my $m5 = $price / 1;
124             my $m6 = $price % 1;
125              
126             # Objects work too
127             my $m7 = $m2 + $m3;
128             my $m8 = $m2 - $m3;
129             my $m9 = $m2 * $m3;
130             my $m10 = $m2 / $m3;
131              
132             # Modifies in place
133             $price += 1;
134             $price -= 1;
135             $price *= 1;
136             $price /= 1;
137              
138             # Compares against numbers
139             print "Currency > 2 \n" if ($m2 > 2);
140             print "Currency < 3 \n" if ($m2 < 3);
141             print "Currency == 2.2 \n" if ($m2 == 2.2);
142              
143             # And strings
144             print "Currency gt \$2.00 \n" if ($m2 gt '$2.00');
145             print "Currency lt \$3.00 \n" if ($m2 lt '$3.00');
146             print "Currency eq \$2.20 \n" if ($m2 eq '$2.20');
147              
148             # and objects
149             print "Currency m2 > m3 \n" if ($m2 > $m3);
150             print "Currency m3 lt m2 \n" if ($m3 lt $m2);
151              
152             print $price->as_string('FMT_SYMBOL'); # $1.20
153              
154             =cut
155              
156             sub BUILD {
157 193     193 0 27099 my ($self) = @_;
158              
159 193         286 my $exp = 0;
160 193         2542 my $dec = $self->value->copy->bmod(1);
161 193 100       55189 $exp = $dec->exponent->babs if ($dec);
162 193         9087 my $prec = Math::BigInt->new($self->_decimal_precision);
163              
164 192 100       9027 Data::Money::BaseException::ExcessivePrecision->throw if ($exp > $prec);
165             }
166              
167             =head1 METHODS
168              
169             =head2 name()
170              
171             Returns C object currency name.
172              
173             =head2 code($currency_code)
174              
175             Gets/sets the three letter currency code for the current currency object.Defaults
176             to USD.
177              
178             =head2 value()
179              
180             Returns the amount. Defaults to 0.
181              
182             =head2 format($string)
183              
184             Gets/sets the format to be used when C is called. See L
185             for the available formatting options. Defaults to C.
186              
187             =cut
188              
189             sub name {
190 0     0 1 0 my ($self) = @_;
191              
192 0         0 my $name = code2currency($self->code);
193             ## Fix for older Locale::Currency w/mispelled Candian
194 0         0 $name =~ s/Candian/Canadian/ms;
195              
196 0         0 return $name;
197             }
198              
199             =head2 clone(%params)
200              
201             Returns a clone (new instance) of this C object. You may optionally
202             specify some of the attributes to overwrite.
203              
204             $currency->clone({ value => 100 }); # Clones all fields but changes value to 100
205              
206             =cut
207              
208             sub clone {
209 125     125 1 7812 my ($self, %param) = @_;
210              
211 125 50 33     2012 $param{code} = $self->code unless (exists $param{code} && defined $param{code});
212 125 50 33     2220 $param{format} = $self->format unless (exists $param{format} && defined $param{format});
213 125 50 33     883 $param{value} = $self->value unless (exists $param{value} && defined $param{value});
214 125         1645 return $self->new(\%param);
215             }
216              
217             =head2 as_float()
218              
219             Returns C object value without any formatting.
220              
221             =cut
222              
223             # Liberally jacked from Math::Currency
224             sub as_float {
225 51     51 1 1899 my ($self) = @_;
226              
227 51         856 return $self->value->copy->bfround(0 - $self->_decimal_precision)->bstr;
228             }
229              
230             =head2 as_int()
231              
232             Returns the object's value "in pennies" (in the US at least). It strips the value
233             of formatting using C and of any decimals.
234              
235             =cut
236              
237             # Liberally jacked from Math::Currency
238             sub as_int {
239 33     33 1 55 my ($self) = @_;
240              
241 33         53 (my $str = $self->as_float) =~ s/\.//omsx;
242 33         5582 $str =~ s/^(\-?)0+/$1/omsx;
243 33 100       143 return $str eq '' ? '0' : $str;
244             }
245              
246             =head2 absolute()
247              
248             Returns a new C object with the value set to the absolute value of
249             the original.
250              
251             =cut
252              
253             sub absolute {
254 22     22 1 659 my ($self) = @_;
255              
256 22         333 return $self->clone(value => abs $self->value);
257             }
258              
259             =head2 negate()
260              
261             Performs the negation operation, returning a new C object with the
262             opposite value (1 to -1, -2 to 2, etc).
263              
264             =cut
265              
266             sub negate {
267 7     7 1 1572 my ($self) = @_;
268              
269 7 100       124 return $self->absolute if ($self->value < 0);
270              
271 3         554 my $val = 0 - $self->value;
272 3         598 return $self->clone(value => $val);
273             }
274              
275             =head2 add($num)
276              
277             Adds the specified amount to this C object and returns a new C
278             object. You can supply either a number or a C object. Note that this B
279             modify the existing object.
280              
281             =cut
282              
283             sub add {
284 9     9 1 1308 my $self = shift;
285 9   50     19 my $num = shift || 0;
286              
287 9 100       27 if (ref($num) eq ref($self)) {
288 3 100       50 Data::Money::BaseException::MismatchCurrencyType->throw
289             if ($self->code ne $num->code);
290              
291 1         22 return $self->clone(value => $self->value->copy->badd($num->value));
292             }
293              
294 6         120 return $self->clone(value => $self->value->copy->badd($self->clone(value => $num)->value))
295             }
296              
297             =head2 add_in_place($num)
298              
299             Adds the specified amount to this C object, modifying its value. You
300             can supply either a number or a C object. Note that this B
301             modify the existing object.
302              
303             =cut
304              
305             sub add_in_place {
306 3     3 1 723 my ($self, $num) = @_;
307              
308 3 100       10 if (ref($num) eq ref($self)) {
309 2 100       39 Data::Money::BaseException::MismatchCurrencyType->throw
310             if ($self->code ne $num->code);
311              
312 1         23 $self->value($self->value->copy->badd($num->value));
313             } else {
314 1         17 $self->value($self->value->copy->badd($self->clone(value => $num)->value));
315             }
316              
317 2         192 return $self;
318             }
319              
320             =head2 as_string()
321              
322             Returns C object as string.There is an alias C as well.
323              
324             =cut
325              
326             *as_string = \&stringify;
327             sub stringify {
328 16     16 0 1286 my $self = shift;
329 16   33     290 my $format = shift || $self->format;
330              
331             ## funky eval to get string versions of constants back into the values
332 16         778 eval '$format = Locale::Currency::Format::' . $format;
333              
334 16         265 my $code = $self->code;
335 16 50 0     91 Data::Money::BaseException::InvalidCurrencyCode->throw(
336             {
337             error => 'Invalid currency code: ' . ($code || 'undef')
338             })
339             unless (_is_CurrencyCode($code));
340              
341 16         612 my $utf8 = _to_utf8(
342             Locale::Currency::Format::currency_format($code, $self->absolute->as_float, $format)
343             );
344              
345 16 50       337 if ($self->value < 0) {
346 0         0 return "-$utf8";
347             } else {
348 16         2840 return $utf8;
349             }
350             }
351              
352             =head2 substract($num)
353              
354             Subtracts the specified amount to this C object and returns a new
355             C object. You can supply either a number or a C object.
356             Note that this B modify the existing object.
357              
358             =cut
359              
360             sub subtract {
361 9     9 0 1313 my ($self, $num, $swap) = @_;
362 9   50     19 $num //= 0;
363              
364 9 100       24 if (ref($num) eq ref($self)) {
365 4 100       69 Data::Money::BaseException::MismatchCurrencyType->throw
366             if ($self->code ne $num->code);
367              
368 3         63 return $self->clone(value => $self->value->copy->bsub($num->value));
369             }
370              
371 5         96 my $result = $self->clone(value => $self->value->copy->bsub($self->clone(value => $num)->value));
372 5 100       176 $result = -$result if $swap;
373 5         116 return $result;
374             }
375              
376             =head2 substract_in_place($num)
377              
378             Subtracts the specified amount to this C object,modifying its value.
379             You can supply either a number or a C object. Note that this B
380             modify the existing object.
381              
382             =cut
383              
384             sub subtract_in_place {
385 3     3 0 325 my ($self, $num) = @_;
386              
387 3 100       9 if (ref($num) eq ref($self)) {
388 2 100       35 Data::Money::BaseException::MismatchCurrencyType->throw
389             if ($self->code ne $num->code);
390              
391 1         23 $self->value($self->value->copy->bsub($num->value));
392             } else {
393 1         19 $self->value($self->value->copy->bsub($self->clone(value => $num)->value));
394             }
395              
396 2         340 return $self;
397             }
398              
399             =head2 multiply($num)
400              
401             Multiplies the value of this C object and returns a new C
402             object. You dcan dsupply either a number or a C object. Note that this
403             B modify the existing object.
404              
405             =cut
406              
407             sub multiply {
408 5     5 1 379 my ($self, $num) = @_;
409              
410 5 100       15 if (ref($num) eq ref($self)) {
411 3 100       57 Data::Money::BaseException::MismatchCurrencyType->throw
412             if ($self->code ne $num->code);
413              
414 2         43 return $self->clone(value => $self->value->copy->bmul($num->value));
415             }
416              
417 2         37 return $self->clone(value => $self->value->copy->bmul($self->clone(value => $num)->value))
418             }
419              
420             =head2 multiply_in_place($num)
421              
422             Multiplies the value of this C object, modifying its value. You can
423             supply either a number or a C object. Note that this B modify
424             the existing object.
425              
426             =cut
427              
428             sub multiply_in_place {
429 5     5 1 1778 my ($self, $num) = @_;
430              
431 5 100       16 if (ref($num) eq ref($self)) {
432 3 100       59 Data::Money::BaseException::MismatchCurrencyType->throw
433             if ($self->code ne $num->code);
434              
435 2         72 $self->value($self->value->copy->bmul($num->value));
436             } else {
437 2         39 $self->value($self->value->copy->bmul($self->clone(value => $num)->value));
438             }
439              
440 4         676 return $self;
441             }
442              
443             =head2 divide($num)
444              
445             Divides the value of this C object and returns a new C
446             object. You can supply either a number or a C object. Note that this
447             B modify the existing object.
448              
449             =cut
450              
451             sub divide {
452 5     5 1 358 my ($self, $num) = @_;
453              
454 5 100       15 if (ref($num) eq ref($self)) {
455 3 100       58 Data::Money::BaseException::MismatchCurrencyType->throw
456             if ($self->code ne $num->code);
457              
458 2         66 my $val = $self->value->copy->bdiv($num->value);
459 2         913 return $self->clone(value => $self->_round_up($val));
460             }
461              
462 2         36 my $val = $self->value->copy->bdiv($self->clone(value => $num)->value);
463 2         1137 return $self->clone(value => $self->_round_up($val));
464             }
465              
466             =head2 divide_in_place($num)
467              
468             Divides the value of this C object, modifying its value. You can
469             supply either a number or a C object. Note that this B modify
470             the existing object.
471              
472             =cut
473              
474             sub divide_in_place {
475 5     5 1 1610 my ($self, $num) = @_;
476              
477 5         6 my $val;
478 5 100       16 if (ref($num) eq ref($self)) {
479 3 100       56 Data::Money::BaseException::MismatchCurrencyType->throw
480             if ($self->code ne $num->code);
481              
482 2         45 $val = $self->value->copy->bdiv($num->value);
483             } else {
484 2         39 $val = $self->value->copy->bdiv($self->clone(value => $num));
485             }
486              
487 4         2093 $self->value($self->_round_up($val));
488              
489 4         633 return $self;
490             }
491              
492             =head2 modulo($num)
493              
494             Performs the modulo operation on this C object, returning a new C
495             object with the value of the remainder.
496              
497             =cut
498              
499             sub modulo {
500 1     1 1 243 my ($self, $num) = @_;
501              
502 1 50       5 if (ref($num) eq ref($self)) {
503 1 50       20 Data::Money::BaseException::MismatchCurrencyType->throw
504             if ($self->code ne $num->code);
505              
506 0         0 my $val = $self->value->copy->bmod($num->value);
507 0         0 return $self->clone(value => $val);
508             }
509              
510 0         0 my $val = $self->value->copy->bmod($self->clone(value => $num)->value);
511 0         0 return $self->clone(value => $val);
512             }
513              
514             =head2 three_way_compare($num)
515              
516             Compares a C object to another C object, or anything it
517             is capable of coercing - numbers, numerical strings, or L objects.
518             Both numerical and string comparators work.
519              
520             =cut
521              
522             sub three_way_compare {
523 72     72 1 17841 my ($self, $num, $swap) = @_;
524 72   50     142 $num //= 0;
525              
526 72         83 my $other;
527 72 100       145 if (ref($num) eq ref($self)) {
528 16         21 $other = $num;
529             } else {
530             # we clone here to ensure that if we're comparing a number to
531             # an object, that the currency codes match (and we don't just
532             # get the default).
533 56         112 $other = $self->clone(value => $num);
534             }
535              
536 72 100       3932 Data::Money::BaseException::MismatchCurrencyType->throw(
537             {
538             error => 'Unable to compare different currency types.'
539             })
540             if ($self->code ne $other->code);
541              
542 68 100       1354 return $swap
543             ? $other->value->copy->bfround( 0 - $self->_decimal_precision )
544             <=> $self->value->copy->bfround( 0 - $self->_decimal_precision )
545             : $self->value->copy->bfround( 0 - $self->_decimal_precision )
546             <=> $other->value->copy->bfround( 0 - $self->_decimal_precision );
547             }
548              
549             #
550             #
551             # PRIVATE METHODS
552              
553             sub _decimal_precision {
554 380     380   13211 my ($self, $code) = @_;
555              
556 380   33     6045 $code ||= $self->code;
557              
558 380         2205 my $format;
559             ## funky eval to get string versions of constants back into the values
560 380         4690 eval '$format = Locale::Currency::Format::' . $self->format;
561              
562 380 100 50     1426 Data::Money::BaseException::InvalidCurrencyCode->throw(
563             {
564             error => 'Invalid currency code: ' . ($code || 'undef')
565             })
566             unless (_is_CurrencyCode($code));
567              
568 379   100     13724 return Locale::Currency::Format::decimal_precision($code) || 0;
569             }
570              
571             sub _round_up {
572 8     8   13 my ($self, $val) = @_;
573              
574 8         134 my $prec = Locale::Currency::Format::decimal_precision($self->code);
575 8         122 return sprintf('%.0'.$prec.'f', _round($val, $prec*-1));
576             }
577              
578             sub _to_utf8 {
579 16     16   2596 my $value = shift;
580              
581 16 50       44 if ($] >= 5.008) {
582 8     8   19306 use Encode;
  8         47259  
  8         1757  
583 16         48 decode('UTF-8', $value);
584             };
585              
586 16         1051 return $value;
587             };
588              
589             sub _is_CurrencyCode {
590 396     396   623 my ($code) = @_;
591              
592 396 50       744 return 0 unless defined $code;
593              
594 396         850 return defined code2currency($code, 'alpha');
595             }
596              
597             # http://www.perlmonks.org/?node_id=24335
598             sub _round {
599 8     8   14 my ($number, $places) = @_;
600              
601 8 50       17 my $sign = ($number < 0) ? '-' : '';
602 8         1335 my $abs = abs($number);
603              
604 8 50       232 if ($places < 0) {
605 8         9 $places *= -1;
606 8         28 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