/
Euro.php
130 lines (105 loc) 路 3.02 KB
/
Euro.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<?php
declare( strict_types = 1 );
namespace WMDE\Euro;
use InvalidArgumentException;
/**
* @licence GNU GPL v2+
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
final class Euro {
private const DECIMAL_COUNT = 2;
private const CENTS_PER_EURO = 100;
private $cents;
/**
* @param int $cents
* @throws InvalidArgumentException
*/
private function __construct( int $cents ) {
if ( $cents < 0 ) {
throw new InvalidArgumentException( 'Amount needs to be positive' );
}
$this->cents = $cents;
}
/**
* @param int $cents
* @return self
* @throws InvalidArgumentException
*/
public static function newFromCents( int $cents ): self {
return new self( $cents );
}
/**
* Constructs a Euro object from a string representation such as "13.37".
*
* This method takes into account the errors that can arise from floating
* point number usage. Amounts with too many decimals are rounded to the
* nearest whole euro cent amount.
*
* @param string $euroAmount
* @return self
* @throws InvalidArgumentException
*/
public static function newFromString( string $euroAmount ): self {
if ( !is_numeric( $euroAmount ) ) {
throw new InvalidArgumentException( 'Not a number' );
}
$parts = explode( '.', $euroAmount, 2 );
$euros = (int)$parts[0];
$cents = self::centsFromString( $parts[1] ?? '0' );
return new self( $euros * self::CENTS_PER_EURO + $cents );
}
private static function centsFromString( string $cents ): int {
if ( strlen( $cents ) > self::DECIMAL_COUNT ) {
return self::roundCentsToInt( $cents );
}
// Turn .1 into .10, so it ends up as 10 cents
return (int)str_pad( $cents, self::DECIMAL_COUNT, '0' );
}
private static function roundCentsToInt( string $cents ): int {
$centsInt = (int)substr( $cents, 0, self::DECIMAL_COUNT );
if ( (int)$cents[self::DECIMAL_COUNT] >= 5 ) {
$centsInt++;
}
return $centsInt;
}
/**
* This method takes into account the errors that can arise from floating
* point number usage. Amounts with too many decimals are rounded to the
* nearest whole euro cent amount.
*
* @param float $euroAmount
* @return self
* @throws InvalidArgumentException
*/
public static function newFromFloat( float $euroAmount ): self {
return new self( intval(
round(
round( $euroAmount, self::DECIMAL_COUNT ) * self::CENTS_PER_EURO,
0
)
) );
}
/**
* @param int $euroAmount
* @return self
* @throws InvalidArgumentException
*/
public static function newFromInt( int $euroAmount ): self {
return new self( $euroAmount * self::CENTS_PER_EURO );
}
public function getEuroCents(): int {
return $this->cents;
}
public function getEuroFloat(): float {
return $this->cents / self::CENTS_PER_EURO;
}
/**
* Returns the euro amount as string with two decimals always present in format "42.00".
*/
public function getEuroString(): string {
return number_format( $this->getEuroFloat(), self::DECIMAL_COUNT, '.', '' );
}
public function equals( Euro $euro ): bool {
return $this->cents === $euro->cents;
}
}