Completed
Push — master ( 842449...fcd89a )
by Arnold
02:08
created

DateExtension::localDateTime()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 8
nc 5
nop 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
    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
    /**
49
     * Get configured intl date formatter.
50
     * 
51
     * @param string|null $dateFormat
52
     * @param string|null $timeFormat
53
     * @param string      $calendar
54
     * @return \IntlDateFormatter
55
     */
56
    protected function getDateFormatter($dateFormat, $timeFormat, $calendar)
57
    {
58
        $datetype = isset($dateFormat) ? $this->getFormat($dateFormat, $calendar) : null;
0 ignored issues
show
Unused Code introduced by
The call to DateExtension::getFormat() has too many arguments starting with $calendar.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
59
        $timetype = isset($timeFormat) ? $this->getFormat($timeFormat, $calendar) : null;
0 ignored issues
show
Unused Code introduced by
The call to DateExtension::getFormat() has too many arguments starting with $calendar.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
60
        
61
        $pattern = null;
62
        
63
        if ($datetype === null || $timetype === null) {
64
            $pattern = $this->getDatePattern(
65
                isset($datetype) ? $datetype : ($dateFormat ?: \IntlDateFormatter::SHORT),
66
                isset($timetype) ? $timetype : ($timeFormat ?: \IntlDateFormatter::SHORT)
67
            );
68
        }
69
        
70
        $calendarConst = $calendar === 'traditional' ? \IntlDateFormatter::TRADITIONAL : \IntlDateFormatter::GREGORIAN;
71
        
72
        return new \IntlDateFormatter(
73
            \Locale::getDefault(),
74
            $datetype,
75
            $timetype,
76
            null,
77
            $calendarConst,
78
            $pattern
79
        );
80
    }
81
    
82
    /**
83
     * Format the date/time value as a string based on the current locale
84
     * 
85
     * @param string $format    'short', 'medium', 'long', 'full'
86
     * @return int|null
87
     */
88
    protected function getFormat($format)
89
    {
90
        switch ($format) {
91
            case false:    $type = \IntlDateFormatter::NONE; break;
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $format of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
92
            case 'short':  $type = \IntlDateFormatter::SHORT; break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
93
            case 'medium': $type = \IntlDateFormatter::MEDIUM; break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
94
            case 'long':   $type = \IntlDateFormatter::LONG; break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
95
            case 'full':   $type = \IntlDateFormatter::FULL; break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
96
            default:       $type = null;
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
97
        }
98
        
99
        return $type;
100
    }
101
    
102
    /**
103
     * Default date pattern is short date pattern with 4 digit year
104
     * 
105
     * @param int|string $datetype
106
     * @param int|string $timetype
107
     * @param int        $calendar
108
     * @return string
109
     */
110
    protected function getDatePattern($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
111
    {
112
        if (
113
            (is_int($datetype) && $datetype !== \IntlDateFormatter::NONE) ||
114
            (is_int($timetype) && $timetype !== \IntlDateFormatter::NONE)
115
        ){
116
            $pattern = \IntlDateFormatter::create(
117
                \Locale::getDefault(),
118
                is_int($datetype) ? $datetype : \IntlDateFormatter::NONE,
119
                is_int($timetype) ? $timetype : \IntlDateFormatter::NONE,
120
                \IntlTimeZone::getGMT(),
121
                $calendar
122
            )->getPattern();
123
        } else {
124
            $pattern = null;
125
        }
126
        
127
        if (is_string($datetype)) {
128
            $pattern = trim($datetype . ' ' . $pattern);
129
        }
130
131
        if (is_string($timetype)) {
132
            $pattern = trim($pattern . ' ' . $timetype);
133
        }
134
        
135
        return preg_replace('/\byy?\b/', 'yyyy', $pattern);
136
    }
137
138
    /**
139
     * Format the date and/or time value as a string based on the current locale
140
     * 
141
     * @param DateTime|int|string $date
142
     * @param string              $dateFormat  null, 'short', 'medium', 'long', 'full' or pattern
143
     * @param string              $timeFormat  null, 'short', 'medium', 'long', 'full' or pattern
144
     * @param string              $calendar    'gregorian' or 'traditional'
145
     * @return string
146
     */
147
    protected function formatLocal($date, $dateFormat, $timeFormat, $calendar = 'gregorian')
148
    {
149
        if (!isset($date)) {
150
            return null;
151
        }
152
        
153 View Code Duplication
        if (!$date instanceof \DateTime) {
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...
154
            $date = is_int($date) ? \DateTime::createFromFormat('U', $date) : new \DateTime((string)$date);
155
        }
156
        
157
        $formatter = $this->getDateFormatter($dateFormat, $timeFormat, $calendar);
158
        
159
        return $formatter->format($date->getTimestamp());
160
    }
161
162
    /**
163
     * Format the date value as a string based on the current locale
164
     * 
165
     * @param DateTime|int|string $date
166
     * @param string              $format    null, 'short', 'medium', 'long', 'full' or pattern
167
     * @param string              $calendar  'gregorian' or 'traditional'
168
     * @return string
169
     */
170
    public function localDate($date, $format = null, $calendar = 'gregorian')
171
    {
172
        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...
173
    }
174
    
175
    /**
176
     * Format the time value as a string based on the current locale
177
     * 
178
     * @param DateTime|int|string $date
179
     * @param string              $format    'short', 'medium', 'long', 'full' or pattern
180
     * @param string              $calendar  'gregorian' or 'traditional'
181
     * @return string
182
     */
183
    public function localTime($date, $format='short', $calendar='gregorian')
184
    {
185
        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...
186
    }
187
188
    /**
189
     * Format the date/time value as a string based on the current locale
190
     * 
191
     * @param DateTime|int|string $date
192
     * @param string              $format    date format, pattern or ['date'=>format, 'time'=>format)
193
     * @param string              $calendar  'gregorian' or 'traditional'
194
     * @return string
195
     */
196
    public function localDateTime($date, $format = null, $calendar = 'gregorian')
197
    {
198
        if (is_array($format) || $format instanceof \stdClass || !isset($format)) {
199
            $formatDate = isset($format['date']) ? $format['date'] : null;
200
            $formatTime = isset($format['time']) ? $format['time'] : 'short';
201
        } else {
202
            $formatDate = $format;
203
            $formatTime = false;
204
        }
205
        
206
        return $this->formatLocal($date, $formatDate, $formatTime, $calendar);
207
    }
208
    
209
210
    /**
211
     * Split duration into seconds, minutes, hours, days, weeks and years.
212
     * 
213
     * @param int $seconds
214
     * @return array
215
     */
216
    protected function splitDuration($seconds, $max)
217
    {
218
        if ($max < 1 || $seconds < 60) {
219
            return [$seconds];
220
        }
221
        
222
        $minutes = floor($seconds / 60);
223
        $seconds = $seconds % 60;
224
        if ($max < 2 || $minutes < 60) {
225
            return [$seconds, $minutes];
226
        }
227
        
228
        $hours = floor($minutes / 60);
229
        $minutes = $minutes % 60;
230 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...
231
            return [$seconds, $minutes, $hours];
232
        }
233
        
234
        $days = floor($hours / 24);
235
        $hours = $hours % 24;
236 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...
237
            return [$seconds, $minutes, $hours, $days]; 
238
        }
239
        
240
        $weeks = floor($days / 7);
241
        $days = $days % 7;
242 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...
243
            return [$seconds, $minutes, $hours, $days, $weeks];
244
        }
245
        
246
        $years = floor($weeks / 52);
247
        $weeks = $weeks % 52;
248
        return [$seconds, $minutes, $hours, $days, $weeks, $years];
249
    }
250
    
251
    /**
252
     * Calculate duration from seconds.
253
     * One year is seen as exactly 52 weeks.
254
     * 
255
     * Use null to skip a unit.
256
     * 
257
     * @param int    $value     Time in seconds
258
     * @param array  $units     Time units (seconds, minutes, hours, days, weeks, years)
259
     * @param string $seperator
260
     * @return string
261
     */
262
    public function duration($value, $units = ['s', 'm', 'h', 'd', 'w', 'y'], $seperator = ' ')
263
    {
264
        if (!isset($value)) {
265
            return null;
266
        }
267
        
268
        list($seconds, $minutes, $hours, $days, $weeks, $years) =
269
            $this->splitDuration($value, count($units) - 1) + array_fill(0, 6, null);
270
        
271
        $duration = '';
272 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...
273
            $duration .= $seperator . $years . $units[5];
274
        }
275
        
276 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...
277
            $duration .= $seperator . $weeks . $units[4];
278
        }
279
        
280 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...
281
            $duration .= $seperator . $days . $units[3];
282
        }
283
        
284 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...
285
            $duration .= $seperator . $hours . $units[2];
286
        }
287
        
288 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...
289
            $duration .= $seperator . $minutes . $units[1];
290
        }
291
        
292 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...
293
            $duration .= $seperator . $seconds . $units[0];
294
        }
295
        
296
        return trim($duration, $seperator);
297
    }
298
299
    /**
300
     * Get the age (in years) based on a date.
301
     * 
302
     * @param DateTime|string $date
303
     * @return int
304
     */
305
    public function age($date)
306
    {
307
        if (!isset($date)) {
308
            return null;
309
        }
310
        
311 View Code Duplication
        if (!$date instanceof \DateTime) {
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...
312
            $date = is_int($date) ? \DateTime::createFromFormat('U', $date) : new \DateTime((string)$date);
313
        }
314
        
315
        return $date->diff(new \DateTime())->format('%y');
316
    }
317
}
318