File Coverage

blib/lib/Data/Money.pm
Criterion Covered Total %
statement 154 168 91.6
branch 61 72 84.7
condition 11 27 40.7
subroutine 36 40 90.0
pod 14 18 77.7
total 276 325 84.9


line stmt bran cond sub pod time code
1             package Data::Money;
2              
3             $Data::Money::VERSION = '0.19';
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.19
13              
14             =cut
15              
16 8     8   531688 use 5.006;
  8         77  
17 8     8   4724 use Moo;
  8         96659  
  8         40  
18 8     8   16070 use namespace::clean;
  8         98869  
  8         53  
19              
20 8     8   7380 use Data::Dumper;
  8         54770  
  8         525  
21 8     8   9069 use Math::BigFloat;
  8         465063  
  8         52  
22 8     8   199346 use Data::Money::BaseException::MismatchCurrencyType;
  8         37  
  8         310  
23 8     8   3912 use Data::Money::BaseException::ExcessivePrecision;
  8         26  
  8         262  
24 8     8   3855 use Data::Money::BaseException::InvalidCurrencyCode;
  8         24  
  8         261  
25 8     8   3733 use Data::Money::BaseException::InvalidCurrencyFormat;
  8         22  
  8         325  
26 8     8   4907 use Locale::Currency::Format;
  8         39245  
  8         844  
27 8     8   33989 use Locale::Currency qw(code2currency);
  8         135662  
  8         2014  
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   620 'bool' => sub { shift->as_int; },
42             '<=>' => \&three_way_compare,
43             'cmp' => \&three_way_compare,
44 2     2   637 'abs' => sub { shift->absolute },
45 0     0   0 '=' => sub { shift->clone },
46 8         183 'neg' => \&negate,
47 8     8   73 fallback => 1;
  8         20  
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 33527 my ($self) = @_;
158              
159 193         341 my $exp = 0;
160 193         3237 my $dec = $self->value->copy->bmod(1);
161 193 100       68959 $exp = $dec->exponent->babs if ($dec);
162 193         11283 my $prec = Math::BigInt->new($self->_decimal_precision);
163              
164 192 100       11262 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 10068 my ($self, %param) = @_;
210              
211 125 50 33     2561 $param{code} = $self->code unless (exists $param{code} && defined $param{code});
212 125 50 33     2718 $param{format} = $self->format unless (exists $param{format} && defined $param{format});
213 125 50 33     1096 $param{value} = $self->value unless (exists $param{value} && defined $param{value});
214 125         2023 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 2350 my ($self) = @_;
226              
227 51         1115 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 69 my ($self) = @_;
240              
241 33         81 (my $str = $self->as_float) =~ s/\.//omsx;
242 33         5720 $str =~ s/^(\-?)0+/$1/omsx;
243 33 100       187 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 871 my ($self) = @_;
255              
256 22         419 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 1974 my ($self) = @_;
268              
269 7 100       163 return $self->absolute if ($self->value < 0);
270              
271 3         710 my $val = 0 - $self->value;
272 3         785 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 2044 my $self = shift;
285 9   50     29 my $num = shift || 0;
286              
287 9 100       58 if (ref($num) eq ref($self)) {
288 3 100       66 Data::Money::BaseException::MismatchCurrencyType->throw
289             if ($self->code ne $num->code);
290              
291 1         30 return $self->clone(value => $self->value->copy->badd($num->value));
292             }
293              
294 6         173 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 932 my ($self, $num) = @_;
307              
308 3 100       14 if (ref($num) eq ref($self)) {
309 2 100       51 Data::Money::BaseException::MismatchCurrencyType->throw
310             if ($self->code ne $num->code);
311              
312 1         29 $self->value($self->value->copy->badd($num->value));
313             } else {
314 1         23 $self->value($self->value->copy->badd($self->clone(value => $num)->value));
315             }
316              
317 2         243 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 1575 my $self = shift;
329 16   33     373 my $format = shift || $self->format;
330              
331             ## funky eval to get string versions of constants back into the values
332 16         1002 eval '$format = Locale::Currency::Format::' . $format;
333              
334 16         310 my $code = $self->code;
335 16 50 0     113 Data::Money::BaseException::InvalidCurrencyCode->throw(
336             {
337             error => 'Invalid currency code: ' . ($code || 'undef')
338             })
339             unless (_is_CurrencyCode($code));
340              
341 16         722 my $utf8 = _to_utf8(
342             Locale::Currency::Format::currency_format($code, $self->absolute->as_float, $format)
343             );
344              
345 16 50       408 if ($self->value < 0) {
346 0         0 return "-$utf8";
347             } else {
348 16         3473 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 1744 my ($self, $num, $swap) = @_;
362 9   50     30 $num //= 0;
363              
364 9 100       37 if (ref($num) eq ref($self)) {
365 4 100       90 Data::Money::BaseException::MismatchCurrencyType->throw
366             if ($self->code ne $num->code);
367              
368 3         93 return $self->clone(value => $self->value->copy->bsub($num->value));
369             }
370              
371 5         148 my $result = $self->clone(value => $self->value->copy->bsub($self->clone(value => $num)->value));
372 5 100       235 $result = -$result if $swap;
373 5         156 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 447 my ($self, $num) = @_;
386              
387 3 100       25 if (ref($num) eq ref($self)) {
388 2 100       46 Data::Money::BaseException::MismatchCurrencyType->throw
389             if ($self->code ne $num->code);
390              
391 1         29 $self->value($self->value->copy->bsub($num->value));
392             } else {
393 1         22 $self->value($self->value->copy->bsub($self->clone(value => $num)->value));
394             }
395              
396 2         417 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 518 my ($self, $num) = @_;
409              
410 5 100       21 if (ref($num) eq ref($self)) {
411 3 100       74 Data::Money::BaseException::MismatchCurrencyType->throw
412             if ($self->code ne $num->code);
413              
414 2         54 return $self->clone(value => $self->value->copy->bmul($num->value));
415             }
416              
417 2         50 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 2351 my ($self, $num) = @_;
430              
431 5 100       19 if (ref($num) eq ref($self)) {
432 3 100       74 Data::Money::BaseException::MismatchCurrencyType->throw
433             if ($self->code ne $num->code);
434              
435 2         54 $self->value($self->value->copy->bmul($num->value));
436             } else {
437 2         50 $self->value($self->value->copy->bmul($self->clone(value => $num)->value));
438             }
439              
440 4         836 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 448 my ($self, $num) = @_;
453              
454 5 100       17 if (ref($num) eq ref($self)) {
455 3 100       73 Data::Money::BaseException::MismatchCurrencyType->throw
456             if ($self->code ne $num->code);
457              
458 2         51 my $val = $self->value->copy->bdiv($num->value);
459 2         1099 return $self->clone(value => $self->_round_up($val));
460             }
461              
462 2         47 my $val = $self->value->copy->bdiv($self->clone(value => $num)->value);
463 2         1496 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 2056 my ($self, $num) = @_;
476              
477 5         10 my $val;
478 5 100       17 if (ref($num) eq ref($self)) {
479 3 100       73 Data::Money::BaseException::MismatchCurrencyType->throw
480             if ($self->code ne $num->code);
481              
482 2         53 $val = $self->value->copy->bdiv($num->value);
483             } else {
484 2         50 $val = $self->value->copy->bdiv($self->clone(value => $num));
485             }
486              
487 4         2692 $self->value($self->_round_up($val));
488              
489 4         758 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 310 my ($self, $num) = @_;
501              
502 1 50       6 if (ref($num) eq ref($self)) {
503 1 50       25 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 23358 my ($self, $num, $swap) = @_;
524 72   50     195 $num //= 0;
525              
526 72         108 my $other;
527 72 100       183 if (ref($num) eq ref($self)) {
528 16         25 $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         150 $other = $self->clone(value => $num);
534             }
535              
536 72 100       5005 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       1780 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   17422 my ($self, $code) = @_;
555              
556 380   33     7578 $code ||= $self->code;
557              
558 380         2752 my $format;
559             ## funky eval to get string versions of constants back into the values
560 380         5962 eval '$format = Locale::Currency::Format::' . $self->format;
561              
562 380 100 50     1740 Data::Money::BaseException::InvalidCurrencyCode->throw(
563             {
564             error => 'Invalid currency code: ' . ($code || 'undef')
565             })
566             unless (_is_CurrencyCode($code));
567              
568 379   100     17522 return Locale::Currency::Format::decimal_precision($code) || 0;
569             }
570              
571             sub _round_up {
572 8     8   17 my ($self, $val) = @_;
573              
574 8         180 my $prec = Locale::Currency::Format::decimal_precision($self->code);
575 8         170 return sprintf('%.0'.$prec.'f', _round($val, $prec*-1));
576             }
577              
578             sub _to_utf8 {
579 16     16   3123 my $value = shift;
580              
581 16 50       50 if ($] >= 5.008) {
582 16         86 require utf8;
583 16         45 utf8::upgrade($value);
584             };
585              
586 16         38 return $value;
587             };
588              
589             sub _is_CurrencyCode {
590 396     396   856 my ($code) = @_;
591              
592 396 50       994 return 0 unless defined $code;
593              
594 396         1156 return defined code2currency($code, 'alpha');
595             }
596              
597             # http://www.perlmonks.org/?node_id=24335
598             sub _round {
599 8     8   16 my ($number, $places) = @_;
600              
601 8 50       26 my $sign = ($number < 0) ? '-' : '';
602 8         1642 my $abs = abs($number);
603              
604 8 50       292 if ($places < 0) {
605 8         15 $places *= -1;
606 8         35 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