Completed
Push — master ( 75299b...657c37 )
by Arnold
02:58
created

DateExtension::getDatePatternFormatter()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.4285
cc 3
eloc 7
nc 1
nop 3
crap 3
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 formatter to create a date and/or time pattern
132
     * 
133
     * @param int|string $datetype
134
     * @param int|string $timetype
135
     * @param int        $calendar
136
     * @return string
137
     */
138 6
    protected function getDatePatternFormatter($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
139
    {
140 6
        return \IntlDateFormatter::create(
141 6
            \Locale::getDefault(),
142 6
            is_int($datetype) ? $datetype : \IntlDateFormatter::NONE,
143 6
            is_int($timetype) ? $timetype : \IntlDateFormatter::NONE,
144 6
            \IntlTimeZone::getGMT(),
145
            $calendar
146
        );
147
    }
148
    
149
    /**
150
     * Get the date and/or time pattern
151
     * Default date pattern is short date pattern with 4 digit year.
152
     * 
153
     * @param int|string $datetype
154
     * @param int|string $timetype
155
     * @param int        $calendar
156
     * @return string
157
     */
158 12
    protected function getDatePattern($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
159
    {
160
        $createPattern =
161 12
            (is_int($datetype) && $datetype !== \IntlDateFormatter::NONE) ||
162 12
            (is_int($timetype) && $timetype !== \IntlDateFormatter::NONE);
163
        
164 12
        $pattern = $createPattern ? $this->getDatePatternFormatter($datetype, $timetype, $calendar)->getPattern() : '';
165
        
166 12
        return trim(
167 12
            (is_string($datetype) ? $datetype . ' ' : '') .
168 12
            preg_replace('/\byy?\b/', 'yyyy', $pattern) .
169 12
            (is_string($timetype) ? ' ' . $timetype : '')
170
        );
171
    }
172
173
    /**
174
     * Format the date and/or time value as a string based on the current locale
175
     * 
176
     * @param \DateTime|int|string $value
177
     * @param string               $dateFormat  null, 'short', 'medium', 'long', 'full' or pattern
178
     * @param string               $timeFormat  null, 'short', 'medium', 'long', 'full' or pattern
179
     * @param string               $calendar    'gregorian' or 'traditional'
180
     * @return string
181
     */
182 31
    protected function formatLocal($value, $dateFormat, $timeFormat, $calendar = 'gregorian')
183
    {
184 31
        if (!isset($value)) {
185 3
            return null;
186
        }
187
        
188 28
        $date = $this->valueToDateTime($value);
189 28
        $formatter = $this->getDateFormatter($dateFormat, $timeFormat, $calendar);
190
        
191 28
        return $formatter->format($date->getTimestamp());
192
    }
193
194
    /**
195
     * Format the date value as a string based on the current locale
196
     * 
197
     * @param DateTime|int|string $date
198
     * @param string              $format    null, 'short', 'medium', 'long', 'full' or pattern
199
     * @param string              $calendar  'gregorian' or 'traditional'
200
     * @return string
201
     */
202 11
    public function localDate($date, $format = null, $calendar = 'gregorian')
203
    {
204 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...
205
    }
206
    
207
    /**
208
     * Format the time value as a string based on the current locale
209
     * 
210
     * @param DateTime|int|string $date
211
     * @param string              $format    'short', 'medium', 'long', 'full' or pattern
212
     * @param string              $calendar  'gregorian' or 'traditional'
213
     * @return string
214
     */
215 11
    public function localTime($date, $format = 'short', $calendar = 'gregorian')
216
    {
217 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...
218
    }
219
220
    /**
221
     * Format the date/time value as a string based on the current locale
222
     * 
223
     * @param DateTime|int|string $date
224
     * @param string              $format    date format, pattern or ['date'=>format, 'time'=>format)
225
     * @param string              $calendar  'gregorian' or 'traditional'
226
     * @return string
227
     */
228 9
    public function localDateTime($date, $format = null, $calendar = 'gregorian')
229
    {
230 9
        if (is_array($format) || $format instanceof \stdClass || !isset($format)) {
231 6
            $formatDate = isset($format['date']) ? $format['date'] : null;
232 6
            $formatTime = isset($format['time']) ? $format['time'] : 'short';
233
        } else {
234 3
            $formatDate = $format;
235 3
            $formatTime = false;
236
        }
237
        
238 9
        return $this->formatLocal($date, $formatDate, $formatTime, $calendar);
239
    }
240
    
241
242
    /**
243
     * Split duration into seconds, minutes, hours, days, weeks and years.
244
     * 
245
     * @param int $seconds
246
     * @return array
247
     */
248 13
    protected function splitDuration($seconds, $max)
249
    {
250 13
        if ($max < 1 || $seconds < 60) {
251 1
            return [$seconds];
252
        }
253
        
254 12
        $minutes = floor($seconds / 60);
255 12
        $seconds = $seconds % 60;
256 12
        if ($max < 2 || $minutes < 60) {
257 2
            return [$seconds, $minutes];
258
        }
259
        
260 10
        $hours = floor($minutes / 60);
261 10
        $minutes = $minutes % 60;
262 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...
263 4
            return [$seconds, $minutes, $hours];
264
        }
265
        
266 6
        $days = floor($hours / 24);
267 6
        $hours = $hours % 24;
268 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...
269 2
            return [$seconds, $minutes, $hours, $days]; 
270
        }
271
        
272 4
        $weeks = floor($days / 7);
273 4
        $days = $days % 7;
274 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...
275 2
            return [$seconds, $minutes, $hours, $days, $weeks];
276
        }
277
        
278 2
        $years = floor($weeks / 52);
279 2
        $weeks = $weeks % 52;
280 2
        return [$seconds, $minutes, $hours, $days, $weeks, $years];
281
    }
282
    
283
    /**
284
     * Calculate duration from seconds.
285
     * One year is seen as exactly 52 weeks.
286
     * 
287
     * Use null to skip a unit.
288
     * 
289
     * @param int    $value     Time in seconds
290
     * @param array  $units     Time units (seconds, minutes, hours, days, weeks, years)
291
     * @param string $seperator
292
     * @return string
293
     */
294 14
    public function duration($value, $units = ['s', 'm', 'h', 'd', 'w', 'y'], $seperator = ' ')
295
    {
296 14
        if (!isset($value)) {
297 1
            return null;
298
        }
299
        
300
        list($seconds, $minutes, $hours, $days, $weeks, $years) =
301 13
            $this->splitDuration($value, count($units) - 1) + array_fill(0, 6, null);
302
        
303 13
        $duration = '';
304 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...
305 2
            $duration .= $seperator . $years . $units[5];
306
        }
307
        
308 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...
309 3
            $duration .= $seperator . $weeks . $units[4];
310
        }
311
        
312 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...
313 4
            $duration .= $seperator . $days . $units[3];
314
        }
315
        
316 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...
317 7
            $duration .= $seperator . $hours . $units[2];
318
        }
319
        
320 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...
321 8
            $duration .= $seperator . $minutes . $units[1];
322
        }
323
        
324 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...
325 6
            $duration .= $seperator . $seconds . $units[0];
326
        }
327
        
328 13
        return trim($duration, $seperator);
329
    }
330
331
    /**
332
     * Get the age (in years) based on a date.
333
     * 
334
     * @param DateTime|string $value
335
     * @return int
336
     */
337 3
    public function age($value)
338
    {
339 3
        if (!isset($value)) {
340 1
            return null;
341
        }
342
        
343 2
        $date = $this->valueToDateTime($value);
344
        
345 2
        return $date->diff(new \DateTime())->format('%y');
346
    }
347
}
348