Failed Conditions
Push — master ( 05acd6...202119 )
by Arnold
07:31
created

DateExtension::valueToDateTime()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 3
eloc 4
nc 3
nop 1
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
    public function __construct()
14
    {
15
        if (!extension_loaded('intl')) {
16
            throw new \Exception("The Date Twig extension requires the 'intl' PHP extension."); // @codeCoverageIgnore
17
        }
18
    }
19
20
    
21
    /**
22
     * Return extension name
23
     * 
24
     * @return string
25
     */
26
    public function getName()
27
    {
28
        return 'jasny/date';
29
    }
30
31
    /**
32
     * Callback for Twig to get all the filters.
33
     * 
34
     * @return \Twig_Filter[]
35
     */
36
    public function getFilters()
37
    {
38
        return [
39
            new \Twig_SimpleFilter('localdate', [$this, 'localDate']),
40
            new \Twig_SimpleFilter('localtime', [$this, 'localTime']),
41
            new \Twig_SimpleFilter('localdatetime', [$this, 'localDateTime']),
42
            new \Twig_SimpleFilter('duration', [$this, 'duration']),
43
            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
    protected function valueToDateTime($date)
54
    {
55
        if (!$date instanceof \DateTime) {
56
            $date = is_int($date) ? \DateTime::createFromFormat('U', $date) : new \DateTime((string)$date);
57
        }
58
        
59
        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
    protected function getDateFormatter($dateFormat, $timeFormat, $calendar)
71
    {
72
        $datetype = isset($dateFormat) ? $this->getFormat($dateFormat) : null;
73
        $timetype = isset($timeFormat) ? $this->getFormat($timeFormat) : null;
74
        
75
        $pattern = null;
76
        
77
        if ($datetype === null || $timetype === null) {
78
            $pattern = $this->getDatePattern(
79
                isset($datetype) ? $datetype : ($dateFormat ?: \IntlDateFormatter::SHORT),
80
                isset($timetype) ? $timetype : ($timeFormat ?: \IntlDateFormatter::SHORT)
81
            );
82
        }
83
        
84
        $calendarConst = $calendar === 'traditional' ? \IntlDateFormatter::TRADITIONAL : \IntlDateFormatter::GREGORIAN;
85
        
86
        return new \IntlDateFormatter(
87
            \Locale::getDefault(),
88
            $datetype,
89
            $timetype,
90
            null,
91
            $calendarConst,
92
            $pattern
93
        );
94
    }
95
    
96
    /**
97
     * Format the date/time value as a string based on the current locale
98
     * 
99
     * @param string|false $format  'short', 'medium', 'long', 'full', 'none' or false
100
     * @return int|null
101
     */
102
    protected function getFormat($format)
103
    {
104
        if ($format === false) {
105
            $format = 'none';
106
        }
107
        
108
        $types = [
109
            'none' => \IntlDateFormatter::NONE,
110
            'short' => \IntlDateFormatter::SHORT,
111
            'medium' => \IntlDateFormatter::MEDIUM,
112
            'long' => \IntlDateFormatter::LONG,
113
            'full' => \IntlDateFormatter::FULL
114
        ];
115
        
116
        return isset($types[$format]) ? $types[$format] : null;
117
    }
118
    
119
    /**
120
     * Default date pattern is short date pattern with 4 digit year
121
     * 
122
     * @param int|string $datetype
123
     * @param int|string $timetype
124
     * @param int        $calendar
125
     * @return string
126
     */
127
    protected function getDatePattern($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
128
    {
129
        if (
130
            (is_int($datetype) && $datetype !== \IntlDateFormatter::NONE) ||
131
            (is_int($timetype) && $timetype !== \IntlDateFormatter::NONE)
132
        ){
133
            $pattern = \IntlDateFormatter::create(
134
                \Locale::getDefault(),
135
                is_int($datetype) ? $datetype : \IntlDateFormatter::NONE,
136
                is_int($timetype) ? $timetype : \IntlDateFormatter::NONE,
137
                \IntlTimeZone::getGMT(),
138
                $calendar
139
            )->getPattern();
140
        } else {
141
            $pattern = null;
142
        }
143
        
144
        if (is_string($datetype)) {
145
            $pattern = trim($datetype . ' ' . $pattern);
146
        }
147
148
        if (is_string($timetype)) {
149
            $pattern = trim($pattern . ' ' . $timetype);
150
        }
151
        
152
        return preg_replace('/\byy?\b/', 'yyyy', $pattern);
153
    }
154
155
    /**
156
     * Format the date and/or time value as a string based on the current locale
157
     * 
158
     * @param \DateTime|int|string $value
159
     * @param string               $dateFormat  null, 'short', 'medium', 'long', 'full' or pattern
160
     * @param string               $timeFormat  null, 'short', 'medium', 'long', 'full' or pattern
161
     * @param string               $calendar    'gregorian' or 'traditional'
162
     * @return string
163
     */
164
    protected function formatLocal($value, $dateFormat, $timeFormat, $calendar = 'gregorian')
165
    {
166
        if (!isset($value)) {
167
            return null;
168
        }
169
        
170
        $date = $this->valueToDateTime($value);
171
        $formatter = $this->getDateFormatter($dateFormat, $timeFormat, $calendar);
172
        
173
        return $formatter->format($date->getTimestamp());
174
    }
175
176
    /**
177
     * Format the date value as a string based on the current locale
178
     * 
179
     * @param DateTime|int|string $date
180
     * @param string              $format    null, 'short', 'medium', 'long', 'full' or pattern
181
     * @param string              $calendar  'gregorian' or 'traditional'
182
     * @return string
183
     */
184
    public function localDate($date, $format = null, $calendar = 'gregorian')
185
    {
186
        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...
187
    }
188
    
189
    /**
190
     * Format the time value as a string based on the current locale
191
     * 
192
     * @param DateTime|int|string $date
193
     * @param string              $format    'short', 'medium', 'long', 'full' or pattern
194
     * @param string              $calendar  'gregorian' or 'traditional'
195
     * @return string
196
     */
197
    public function localTime($date, $format='short', $calendar='gregorian')
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$format" and equals sign; expected 1 but found 0
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$format"; expected 1 but found 0
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$calendar" and equals sign; expected 1 but found 0
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$calendar"; expected 1 but found 0
Loading history...
198
    {
199
        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...
200
    }
201
202
    /**
203
     * Format the date/time value as a string based on the current locale
204
     * 
205
     * @param DateTime|int|string $date
206
     * @param string              $format    date format, pattern or ['date'=>format, 'time'=>format)
207
     * @param string              $calendar  'gregorian' or 'traditional'
208
     * @return string
209
     */
210
    public function localDateTime($date, $format = null, $calendar = 'gregorian')
211
    {
212
        if (is_array($format) || $format instanceof \stdClass || !isset($format)) {
213
            $formatDate = isset($format['date']) ? $format['date'] : null;
214
            $formatTime = isset($format['time']) ? $format['time'] : 'short';
215
        } else {
216
            $formatDate = $format;
217
            $formatTime = false;
218
        }
219
        
220
        return $this->formatLocal($date, $formatDate, $formatTime, $calendar);
221
    }
222
    
223
224
    /**
225
     * Split duration into seconds, minutes, hours, days, weeks and years.
226
     * 
227
     * @param int $seconds
228
     * @return array
229
     */
230
    protected function splitDuration($seconds, $max)
231
    {
232
        if ($max < 1 || $seconds < 60) {
233
            return [$seconds];
234
        }
235
        
236
        $minutes = floor($seconds / 60);
237
        $seconds = $seconds % 60;
238
        if ($max < 2 || $minutes < 60) {
239
            return [$seconds, $minutes];
240
        }
241
        
242
        $hours = floor($minutes / 60);
243
        $minutes = $minutes % 60;
244 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...
245
            return [$seconds, $minutes, $hours];
246
        }
247
        
248
        $days = floor($hours / 24);
249
        $hours = $hours % 24;
250 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...
251
            return [$seconds, $minutes, $hours, $days]; 
252
        }
253
        
254
        $weeks = floor($days / 7);
255
        $days = $days % 7;
256 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...
257
            return [$seconds, $minutes, $hours, $days, $weeks];
258
        }
259
        
260
        $years = floor($weeks / 52);
261
        $weeks = $weeks % 52;
262
        return [$seconds, $minutes, $hours, $days, $weeks, $years];
263
    }
264
    
265
    /**
266
     * Calculate duration from seconds.
267
     * One year is seen as exactly 52 weeks.
268
     * 
269
     * Use null to skip a unit.
270
     * 
271
     * @param int    $value     Time in seconds
272
     * @param array  $units     Time units (seconds, minutes, hours, days, weeks, years)
273
     * @param string $seperator
274
     * @return string
275
     */
276
    public function duration($value, $units = ['s', 'm', 'h', 'd', 'w', 'y'], $seperator = ' ')
277
    {
278
        if (!isset($value)) {
279
            return null;
280
        }
281
        
282
        list($seconds, $minutes, $hours, $days, $weeks, $years) =
283
            $this->splitDuration($value, count($units) - 1) + array_fill(0, 6, null);
284
        
285
        $duration = '';
286 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...
287
            $duration .= $seperator . $years . $units[5];
288
        }
289
        
290 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...
291
            $duration .= $seperator . $weeks . $units[4];
292
        }
293
        
294 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...
295
            $duration .= $seperator . $days . $units[3];
296
        }
297
        
298 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...
299
            $duration .= $seperator . $hours . $units[2];
300
        }
301
        
302 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...
303
            $duration .= $seperator . $minutes . $units[1];
304
        }
305
        
306 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...
307
            $duration .= $seperator . $seconds . $units[0];
308
        }
309
        
310
        return trim($duration, $seperator);
311
    }
312
313
    /**
314
     * Get the age (in years) based on a date.
315
     * 
316
     * @param DateTime|string $value
317
     * @return int
318
     */
319
    public function age($value)
320
    {
321
        if (!isset($value)) {
322
            return null;
323
        }
324
        
325
        $date = $this->valueToDateTime($value);
326
        
327
        return $date->diff(new \DateTime())->format('%y');
328
    }
329
}
330