Failed Conditions
Push — master ( 89ab07...cd548d )
by Arnold
03:16
created

DateExtension::getDateFormatter()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 15
ccs 8
cts 8
cp 1
rs 8.8571
cc 6
eloc 9
nc 8
nop 3
crap 6
1
<?php
2
3
namespace Jasny\Twig;
4
5
/**
6
 * Format a date based on the current locale in Twig
7
 */
8
class DateExtension extends \Twig_Extension
9
{
10
    /**
11
     * Class constructor
12
     */
13 48
    public function __construct()
14
    {
15 48
        if (!extension_loaded('intl')) {
16
            throw new \Exception("The Date Twig extension requires the 'intl' PHP extension."); // @codeCoverageIgnore
17
        }
18 48
    }
19
20
    
21
    /**
22
     * Return extension name
23
     * 
24
     * @return string
25
     */
26 48
    public function getName()
27
    {
28 48
        return 'jasny/date';
29
    }
30
31
    /**
32
     * Callback for Twig to get all the filters.
33
     * 
34
     * @return \Twig_SimpleFilter[]
35
     */
36 34
    public function getFilters()
37
    {
38
        return [
39 34
            new \Twig_SimpleFilter('localdate', [$this, 'localDate']),
40 34
            new \Twig_SimpleFilter('localtime', [$this, 'localTime']),
41 34
            new \Twig_SimpleFilter('localdatetime', [$this, 'localDateTime']),
42 34
            new \Twig_SimpleFilter('duration', [$this, 'duration']),
43 34
            new \Twig_SimpleFilter('age', [$this, 'age']),
44
        ];
45
    }
46
47
    /**
48
     * Turn a value into a DateTime object
49
     * 
50
     * @param string|int|\DateTime $date
51
     * @return \DateTime
52
     */
53 30
    protected function valueToDateTime($date)
54
    {
55 30
        if (!$date instanceof \DateTime) {
56 30
            $date = is_int($date) ? \DateTime::createFromFormat('U', $date) : new \DateTime((string)$date);
57
        }
58
        
59 30
        return $date;
60
    }
61
    
62
    /**
63
     * Get configured intl date formatter.
64
     * 
65
     * @param string|null $dateFormat
66
     * @param string|null $timeFormat
67
     * @param string      $calendar
68
     * @return \IntlDateFormatter
69
     */
70 28
    protected function getDateFormatter($dateFormat, $timeFormat, $calendar)
71
    {
72 28
        $datetype = isset($dateFormat) ? $this->getFormat($dateFormat) : null;
73 28
        $timetype = isset($timeFormat) ? $this->getFormat($timeFormat) : null;
74
75 28
        $calendarConst = $calendar === 'traditional' ? \IntlDateFormatter::TRADITIONAL : \IntlDateFormatter::GREGORIAN;
76
        
77 28
        $pattern = $this->getDateTimePattern(
78 28
            isset($datetype) ? $datetype : $dateFormat,
79 28
            isset($timetype) ? $timetype : $timeFormat,
80
            $calendarConst
81
        );
82
        
83 28
        return new \IntlDateFormatter(\Locale::getDefault(), $datetype, $timetype, null, $calendarConst, $pattern);
84
    }
85
    
86
    /**
87
     * Format the date/time value as a string based on the current locale
88
     * 
89
     * @param string|false $format  'short', 'medium', 'long', 'full', 'none' or false
90
     * @return int|null
91
     */
92 28
    protected function getFormat($format)
93
    {
94 28
        if ($format === false) {
95 22
            $format = 'none';
96
        }
97
        
98
        $types = [
99 28
            'none' => \IntlDateFormatter::NONE,
100
            'short' => \IntlDateFormatter::SHORT,
101
            'medium' => \IntlDateFormatter::MEDIUM,
102
            'long' => \IntlDateFormatter::LONG,
103
            'full' => \IntlDateFormatter::FULL
104
        ];
105
        
106 28
        return isset($types[$format]) ? $types[$format] : null;
107
    }
108
    
109
    /**
110
     * Get the date/time pattern.
111
     * 
112
     * @param int|string $datetype
113
     * @param int|string $timetype
114
     * @param string $calendar
115
     * @return string
116
     */
117 28
    protected function getDateTimePattern($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
118
    {
119 28
        if (is_int($datetype) && is_int($timetype)) {
120 16
            return null;
121
        }
122
        
123 12
        return $this->getDatePattern(
124 12
            isset($datetype) ? $datetype : \IntlDateFormatter::SHORT,
125 12
            isset($timetype) ? $timetype : \IntlDateFormatter::SHORT,
126
            $calendar
127
        );
128
    }
129
    
130
    /**
131
     * Get the date and/or time pattern
132
     * Default date pattern is short date pattern with 4 digit year.
133
     * 
134
     * @param int|string $datetype
135
     * @param int|string $timetype
136
     * @param int        $calendar
137
     * @return string
138
     */
139 12
    protected function getDatePattern($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
140
    {
141
        if (
142 12
            (is_int($datetype) && $datetype !== \IntlDateFormatter::NONE) ||
143 12
            (is_int($timetype) && $timetype !== \IntlDateFormatter::NONE)
144
        ) {
145 6
            $pattern = \IntlDateFormatter::create(
146 6
                \Locale::getDefault(),
147 6
                is_int($datetype) ? $datetype : \IntlDateFormatter::NONE,
148 6
                is_int($timetype) ? $timetype : \IntlDateFormatter::NONE,
149 6
                \IntlTimeZone::getGMT(),
150
                $calendar
151 6
            )->getPattern();
152
        } else {
153 6
            $pattern = null;
154
        }
155
        
156 12
        if (is_string($datetype)) {
157 6
            $pattern = trim($datetype . ' ' . $pattern);
158
        }
159
160 12
        if (is_string($timetype)) {
161 2
            $pattern = trim($pattern . ' ' . $timetype);
162
        }
163
        
164 12
        return preg_replace('/\byy?\b/', 'yyyy', $pattern);
165
    }
166
167
    /**
168
     * Format the date and/or time value as a string based on the current locale
169
     * 
170
     * @param \DateTime|int|string $value
171
     * @param string               $dateFormat  null, 'short', 'medium', 'long', 'full' or pattern
172
     * @param string               $timeFormat  null, 'short', 'medium', 'long', 'full' or pattern
173
     * @param string               $calendar    'gregorian' or 'traditional'
174
     * @return string
175
     */
176 31
    protected function formatLocal($value, $dateFormat, $timeFormat, $calendar = 'gregorian')
177
    {
178 31
        if (!isset($value)) {
179 3
            return null;
180
        }
181
        
182 28
        $date = $this->valueToDateTime($value);
183 28
        $formatter = $this->getDateFormatter($dateFormat, $timeFormat, $calendar);
184
        
185 28
        return $formatter->format($date->getTimestamp());
186
    }
187
188
    /**
189
     * Format the date value as a string based on the current locale
190
     * 
191
     * @param DateTime|int|string $date
192
     * @param string              $format    null, 'short', 'medium', 'long', 'full' or pattern
193
     * @param string              $calendar  'gregorian' or 'traditional'
194
     * @return string
195
     */
196 11
    public function localDate($date, $format = null, $calendar = 'gregorian')
197
    {
198 11
        return $this->formatLocal($date, $format, false, $calendar);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
199
    }
200
    
201
    /**
202
     * Format the time value as a string based on the current locale
203
     * 
204
     * @param DateTime|int|string $date
205
     * @param string              $format    'short', 'medium', 'long', 'full' or pattern
206
     * @param string              $calendar  'gregorian' or 'traditional'
207
     * @return string
208
     */
209 11
    public function localTime($date, $format = 'short', $calendar = 'gregorian')
210
    {
211 11
        return $this->formatLocal($date, false, $format, $calendar);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
212
    }
213
214
    /**
215
     * Format the date/time value as a string based on the current locale
216
     * 
217
     * @param DateTime|int|string $date
218
     * @param string              $format    date format, pattern or ['date'=>format, 'time'=>format)
219
     * @param string              $calendar  'gregorian' or 'traditional'
220
     * @return string
221
     */
222 9
    public function localDateTime($date, $format = null, $calendar = 'gregorian')
223
    {
224 9
        if (is_array($format) || $format instanceof \stdClass || !isset($format)) {
225 6
            $formatDate = isset($format['date']) ? $format['date'] : null;
226 6
            $formatTime = isset($format['time']) ? $format['time'] : 'short';
227
        } else {
228 3
            $formatDate = $format;
229 3
            $formatTime = false;
230
        }
231
        
232 9
        return $this->formatLocal($date, $formatDate, $formatTime, $calendar);
233
    }
234
    
235
236
    /**
237
     * Split duration into seconds, minutes, hours, days, weeks and years.
238
     * 
239
     * @param int $seconds
240
     * @return array
241
     */
242 13
    protected function splitDuration($seconds, $max)
243
    {
244 13
        if ($max < 1 || $seconds < 60) {
245 1
            return [$seconds];
246
        }
247
        
248 12
        $minutes = floor($seconds / 60);
249 12
        $seconds = $seconds % 60;
250 12
        if ($max < 2 || $minutes < 60) {
251 2
            return [$seconds, $minutes];
252
        }
253
        
254 10
        $hours = floor($minutes / 60);
255 10
        $minutes = $minutes % 60;
256 10 View Code Duplication
        if ($max < 3 || $hours < 24) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
257 4
            return [$seconds, $minutes, $hours];
258
        }
259
        
260 6
        $days = floor($hours / 24);
261 6
        $hours = $hours % 24;
262 6 View Code Duplication
        if ($max < 4 || $days < 7) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
263 2
            return [$seconds, $minutes, $hours, $days]; 
264
        }
265
        
266 4
        $weeks = floor($days / 7);
267 4
        $days = $days % 7;
268 4 View Code Duplication
        if ($max < 5 || $weeks < 52) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
269 2
            return [$seconds, $minutes, $hours, $days, $weeks];
270
        }
271
        
272 2
        $years = floor($weeks / 52);
273 2
        $weeks = $weeks % 52;
274 2
        return [$seconds, $minutes, $hours, $days, $weeks, $years];
275
    }
276
    
277
    /**
278
     * Calculate duration from seconds.
279
     * One year is seen as exactly 52 weeks.
280
     * 
281
     * Use null to skip a unit.
282
     * 
283
     * @param int    $value     Time in seconds
284
     * @param array  $units     Time units (seconds, minutes, hours, days, weeks, years)
285
     * @param string $seperator
286
     * @return string
287
     */
288 14
    public function duration($value, $units = ['s', 'm', 'h', 'd', 'w', 'y'], $seperator = ' ')
289
    {
290 14
        if (!isset($value)) {
291 1
            return null;
292
        }
293
        
294
        list($seconds, $minutes, $hours, $days, $weeks, $years) =
295 13
            $this->splitDuration($value, count($units) - 1) + array_fill(0, 6, null);
296
        
297 13
        $duration = '';
298 13 View Code Duplication
        if (isset($years) && isset($units[5])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
299 2
            $duration .= $seperator . $years . $units[5];
300
        }
301
        
302 13 View Code Duplication
        if (isset($weeks) && isset($units[4])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
303 3
            $duration .= $seperator . $weeks . $units[4];
304
        }
305
        
306 13 View Code Duplication
        if (isset($days) && isset($units[3])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
307 4
            $duration .= $seperator . $days . $units[3];
308
        }
309
        
310 13 View Code Duplication
        if (isset($hours) && isset($units[2])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
311 7
            $duration .= $seperator . $hours . $units[2];
312
        }
313
        
314 13 View Code Duplication
        if (isset($minutes) && isset($units[1])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
315 8
            $duration .= $seperator . $minutes . $units[1];
316
        }
317
        
318 13 View Code Duplication
        if (isset($seconds) && isset($units[0])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
319 6
            $duration .= $seperator . $seconds . $units[0];
320
        }
321
        
322 13
        return trim($duration, $seperator);
323
    }
324
325
    /**
326
     * Get the age (in years) based on a date.
327
     * 
328
     * @param DateTime|string $value
329
     * @return int
330
     */
331 3
    public function age($value)
332
    {
333 3
        if (!isset($value)) {
334 1
            return null;
335
        }
336
        
337 2
        $date = $this->valueToDateTime($value);
338
        
339 2
        return $date->diff(new \DateTime())->format('%y');
340
    }
341
}
342