Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/**
4
 * This file is part of the Carbon package.
5
 *
6
 * (c) Brian Nesbitt <brian@nesbot.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Carbon\Traits;
13
 
14
use Carbon\CarbonInterface;
15
use Carbon\Exceptions\UnknownUnitException;
16
 
17
/**
18
 * Trait Rounding.
19
 *
20
 * Round, ceil, floor units.
21
 *
22
 * Depends on the following methods:
23
 *
24
 * @method static copy()
25
 * @method static startOfWeek(int $weekStartsAt = null)
26
 */
27
trait Rounding
28
{
29
    use IntervalRounding;
30
 
31
    /**
32
     * Round the current instance at the given unit with given precision if specified and the given function.
33
     *
34
     * @param string    $unit
35
     * @param float|int $precision
36
     * @param string    $function
37
     *
38
     * @return CarbonInterface
39
     */
40
    public function roundUnit($unit, $precision = 1, $function = 'round')
41
    {
42
        $metaUnits = [
43
            // @call roundUnit
44
            'millennium' => [static::YEARS_PER_MILLENNIUM, 'year'],
45
            // @call roundUnit
46
            'century' => [static::YEARS_PER_CENTURY, 'year'],
47
            // @call roundUnit
48
            'decade' => [static::YEARS_PER_DECADE, 'year'],
49
            // @call roundUnit
50
            'quarter' => [static::MONTHS_PER_QUARTER, 'month'],
51
            // @call roundUnit
52
            'millisecond' => [1000, 'microsecond'],
53
        ];
54
        $normalizedUnit = static::singularUnit($unit);
55
        $ranges = array_merge(static::getRangesByUnit($this->daysInMonth), [
56
            // @call roundUnit
57
            'microsecond' => [0, 999999],
58
        ]);
59
        $factor = 1;
60
        $initialMonth = $this->month;
61
 
62
        if ($normalizedUnit === 'week') {
63
            $normalizedUnit = 'day';
64
            $precision *= static::DAYS_PER_WEEK;
65
        }
66
 
67
        if (isset($metaUnits[$normalizedUnit])) {
68
            [$factor, $normalizedUnit] = $metaUnits[$normalizedUnit];
69
        }
70
 
71
        $precision *= $factor;
72
 
73
        if (!isset($ranges[$normalizedUnit])) {
74
            throw new UnknownUnitException($unit);
75
        }
76
 
77
        $found = false;
78
        $fraction = 0;
79
        $arguments = null;
80
        $initialValue = null;
81
        $factor = $this->year < 0 ? -1 : 1;
82
        $changes = [];
83
        $minimumInc = null;
84
 
85
        foreach ($ranges as $unit => [$minimum, $maximum]) {
86
            if ($normalizedUnit === $unit) {
87
                $arguments = [$this->$unit, $minimum];
88
                $initialValue = $this->$unit;
89
                $fraction = $precision - floor($precision);
90
                $found = true;
91
 
92
                continue;
93
            }
94
 
95
            if ($found) {
96
                $delta = $maximum + 1 - $minimum;
97
                $factor /= $delta;
98
                $fraction *= $delta;
99
                $inc = ($this->$unit - $minimum) * $factor;
100
 
101
                if ($inc !== 0.0) {
102
                    $minimumInc = $minimumInc ?? ($arguments[0] / pow(2, 52));
103
 
104
                    // If value is still the same when adding a non-zero increment/decrement,
105
                    // it means precision got lost in the addition
106
                    if (abs($inc) < $minimumInc) {
107
                        $inc = $minimumInc * ($inc < 0 ? -1 : 1);
108
                    }
109
 
110
                    // If greater than $precision, assume precision loss caused an overflow
111
                    if ($function !== 'floor' || abs($arguments[0] + $inc - $initialValue) >= $precision) {
112
                        $arguments[0] += $inc;
113
                    }
114
                }
115
 
116
                $changes[$unit] = round(
117
                    $minimum + ($fraction ? $fraction * $function(($this->$unit - $minimum) / $fraction) : 0)
118
                );
119
 
120
                // Cannot use modulo as it lose double precision
121
                while ($changes[$unit] >= $delta) {
122
                    $changes[$unit] -= $delta;
123
                }
124
 
125
                $fraction -= floor($fraction);
126
            }
127
        }
128
 
129
        [$value, $minimum] = $arguments;
130
        $normalizedValue = floor($function(($value - $minimum) / $precision) * $precision + $minimum);
131
 
132
        /** @var CarbonInterface $result */
133
        $result = $this->$normalizedUnit($normalizedValue);
134
 
135
        foreach ($changes as $unit => $value) {
136
            $result = $result->$unit($value);
137
        }
138
 
139
        return $normalizedUnit === 'month' && $precision <= 1 && abs($result->month - $initialMonth) === 2
140
            // Re-run the change in case an overflow occurred
141
            ? $result->$normalizedUnit($normalizedValue)
142
            : $result;
143
    }
144
 
145
    /**
146
     * Truncate the current instance at the given unit with given precision if specified.
147
     *
148
     * @param string    $unit
149
     * @param float|int $precision
150
     *
151
     * @return CarbonInterface
152
     */
153
    public function floorUnit($unit, $precision = 1)
154
    {
155
        return $this->roundUnit($unit, $precision, 'floor');
156
    }
157
 
158
    /**
159
     * Ceil the current instance at the given unit with given precision if specified.
160
     *
161
     * @param string    $unit
162
     * @param float|int $precision
163
     *
164
     * @return CarbonInterface
165
     */
166
    public function ceilUnit($unit, $precision = 1)
167
    {
168
        return $this->roundUnit($unit, $precision, 'ceil');
169
    }
170
 
171
    /**
172
     * Round the current instance second with given precision if specified.
173
     *
174
     * @param float|int|string|\DateInterval|null $precision
175
     * @param string                              $function
176
     *
177
     * @return CarbonInterface
178
     */
179
    public function round($precision = 1, $function = 'round')
180
    {
181
        return $this->roundWith($precision, $function);
182
    }
183
 
184
    /**
185
     * Round the current instance second with given precision if specified.
186
     *
187
     * @param float|int|string|\DateInterval|null $precision
188
     *
189
     * @return CarbonInterface
190
     */
191
    public function floor($precision = 1)
192
    {
193
        return $this->round($precision, 'floor');
194
    }
195
 
196
    /**
197
     * Ceil the current instance second with given precision if specified.
198
     *
199
     * @param float|int|string|\DateInterval|null $precision
200
     *
201
     * @return CarbonInterface
202
     */
203
    public function ceil($precision = 1)
204
    {
205
        return $this->round($precision, 'ceil');
206
    }
207
 
208
    /**
209
     * Round the current instance week.
210
     *
211
     * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week
212
     *
213
     * @return CarbonInterface
214
     */
215
    public function roundWeek($weekStartsAt = null)
216
    {
217
        return $this->closest(
218
            $this->avoidMutation()->floorWeek($weekStartsAt),
219
            $this->avoidMutation()->ceilWeek($weekStartsAt)
220
        );
221
    }
222
 
223
    /**
224
     * Truncate the current instance week.
225
     *
226
     * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week
227
     *
228
     * @return CarbonInterface
229
     */
230
    public function floorWeek($weekStartsAt = null)
231
    {
232
        return $this->startOfWeek($weekStartsAt);
233
    }
234
 
235
    /**
236
     * Ceil the current instance week.
237
     *
238
     * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week
239
     *
240
     * @return CarbonInterface
241
     */
242
    public function ceilWeek($weekStartsAt = null)
243
    {
244
        if ($this->isMutable()) {
245
            $startOfWeek = $this->avoidMutation()->startOfWeek($weekStartsAt);
246
 
247
            return $startOfWeek != $this ?
248
                $this->startOfWeek($weekStartsAt)->addWeek() :
249
                $this;
250
        }
251
 
252
        $startOfWeek = $this->startOfWeek($weekStartsAt);
253
 
254
        return $startOfWeek != $this ?
255
            $startOfWeek->addWeek() :
256
            $this->avoidMutation();
257
    }
258
}