Failed Conditions
Pull Request — master (#20)
by Arnold
01:53
created

src/DateExtension.php (3 issues)

1
<?php
2
3
namespace Jasny\Twig;
4
5
use Twig\Extension\AbstractExtension;
6
use Twig\TwigFilter;
7
8
/**
9
 * Format a date based on the current locale in Twig
10
 */
11
class DateExtension extends AbstractExtension
12
{
13
    /**
14
     * Class constructor
15
     */
16 40
    public function __construct()
17
    {
18 40
        if (!extension_loaded('intl')) {
19
            throw new \Exception("The Date Twig extension requires the 'intl' PHP extension."); // @codeCoverageIgnore
20
        }
21 40
    }
22
23
24
    /**
25
     * Return extension name
26
     *
27
     * @return string
28
     */
29
    public function getName()
30
    {
31
        return 'jasny/date';
32
    }
33
34
    /**
35
     * Callback for Twig to get all the filters.
36
     *
37
     * @return \Twig\TwigFilter[]
38
     */
39 30
    public function getFilters()
40
    {
41
        return [
42 30
            new TwigFilter('localdate', [$this, 'localDate']),
43 30
            new TwigFilter('localtime', [$this, 'localTime']),
44 30
            new TwigFilter('localdatetime', [$this, 'localDateTime']),
45 30
            new TwigFilter('duration', [$this, 'duration']),
46 30
            new TwigFilter('age', [$this, 'age']),
47
        ];
48
    }
49
50
    /**
51
     * Turn a value into a DateTime object
52
     *
53
     * @param string|int|\DateTime $date
54
     * @return \DateTime
55
     */
56 22
    protected function valueToDateTime($date)
57
    {
58 22
        if (!$date instanceof \DateTime) {
59 22
            $date = is_int($date) ? \DateTime::createFromFormat('U', $date) : new \DateTime((string)$date);
60
        }
61
62 22
        return $date;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $date could also return false which is incompatible with the documented return type DateTime. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
63
    }
64
65
    /**
66
     * Get configured intl date formatter.
67
     *
68
     * @param string|null $dateFormat
69
     * @param string|null $timeFormat
70
     * @param string      $calendar
71
     * @return \IntlDateFormatter
72
     */
73 20
    protected function getDateFormatter($dateFormat, $timeFormat, $calendar)
74
    {
75 20
        $datetype = isset($dateFormat) ? $this->getFormat($dateFormat) : null;
76 20
        $timetype = isset($timeFormat) ? $this->getFormat($timeFormat) : null;
77
78 20
        $calendarConst = $calendar === 'traditional' ? \IntlDateFormatter::TRADITIONAL : \IntlDateFormatter::GREGORIAN;
79
80 20
        $pattern = $this->getDateTimePattern(
81 20
            isset($datetype) ? $datetype : $dateFormat,
82 20
            isset($timetype) ? $timetype : $timeFormat,
83 20
            $calendarConst
84
        );
85
86 20
        return new \IntlDateFormatter(\Locale::getDefault(), $datetype, $timetype, null, $calendarConst, $pattern);
87
    }
88
89
    /**
90
     * Format the date/time value as a string based on the current locale
91
     *
92
     * @param string|false $format  'short', 'medium', 'long', 'full', 'none' or false
93
     * @return int|null
94
     */
95 20
    protected function getFormat($format)
96
    {
97 20
        if ($format === false) {
98 18
            $format = 'none';
99
        }
100
101
        $types = [
102 20
            'none' => \IntlDateFormatter::NONE,
103
            'short' => \IntlDateFormatter::SHORT,
104
            'medium' => \IntlDateFormatter::MEDIUM,
105
            'long' => \IntlDateFormatter::LONG,
106
            'full' => \IntlDateFormatter::FULL
107
        ];
108
109 20
        return isset($types[$format]) ? $types[$format] : null;
110
    }
111
112
    /**
113
     * Get the date/time pattern.
114
     *
115
     * @param int|string $datetype
116
     * @param int|string $timetype
117
     * @param int        $calendar
118
     * @return string
119
     */
120 20
    protected function getDateTimePattern($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
121
    {
122 20
        if (is_int($datetype) && is_int($timetype)) {
123 10
            return null;
124
        }
125
126 10
        return $this->getDatePattern(
127 10
            isset($datetype) ? $datetype : \IntlDateFormatter::SHORT,
128 10
            isset($timetype) ? $timetype : \IntlDateFormatter::SHORT,
129 10
            $calendar
130
        );
131
    }
132
133
    /**
134
     * Get the formatter to create a date and/or time pattern
135
     *
136
     * @param int|string $datetype
137
     * @param int|string $timetype
138
     * @param int        $calendar
139
     * @return \IntlDateFormatter
140
     */
141 4
    protected function getDatePatternFormatter($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
142
    {
143 4
        return \IntlDateFormatter::create(
144 4
            \Locale::getDefault(),
145 4
            is_int($datetype) ? $datetype : \IntlDateFormatter::NONE,
146 4
            is_int($timetype) ? $timetype : \IntlDateFormatter::NONE,
147 4
            \IntlTimeZone::getGMT(),
148 4
            $calendar
149
        );
150
    }
151
152
    /**
153
     * Get the date and/or time pattern
154
     * Default date pattern is short date pattern with 4 digit year.
155
     *
156
     * @param int|string $datetype
157
     * @param int|string $timetype
158
     * @param int        $calendar
159
     * @return string
160
     */
161 10
    protected function getDatePattern($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
162
    {
163
        $createPattern =
164 10
            (is_int($datetype) && $datetype !== \IntlDateFormatter::NONE) ||
165 10
            (is_int($timetype) && $timetype !== \IntlDateFormatter::NONE);
166
167 10
        $pattern = $createPattern ? $this->getDatePatternFormatter($datetype, $timetype, $calendar)->getPattern() : '';
168
169 10
        return trim(
170 10
            (is_string($datetype) ? $datetype . ' ' : '') .
171 10
            preg_replace('/\byy?\b/', 'yyyy', $pattern) .
172 10
            (is_string($timetype) ? ' ' . $timetype : '')
173
        );
174
    }
175
176
    /**
177
     * Format the date and/or time value as a string based on the current locale
178
     *
179
     * @param \DateTime|int|string $value
180
     * @param string               $dateFormat  null, 'short', 'medium', 'long', 'full' or pattern
181
     * @param string               $timeFormat  null, 'short', 'medium', 'long', 'full' or pattern
182
     * @param string               $calendar    'gregorian' or 'traditional'
183
     * @return string
184
     */
185 23
    protected function formatLocal($value, $dateFormat, $timeFormat, $calendar = 'gregorian')
186
    {
187 23
        if (!isset($value)) {
188 3
            return null;
189
        }
190
191 20
        $date = $this->valueToDateTime($value);
192 20
        $formatter = $this->getDateFormatter($dateFormat, $timeFormat, $calendar);
193
194 20
        return $formatter->format($date->getTimestamp());
195
    }
196
197
    /**
198
     * Format the date value as a string based on the current locale
199
     *
200
     * @param DateTime|int|string $date
0 ignored issues
show
The type Jasny\Twig\DateTime was not found. Did you mean DateTime? If so, make sure to prefix the type with \.
Loading history...
201
     * @param string              $format    null, 'short', 'medium', 'long', 'full' or pattern
202
     * @param string              $calendar  'gregorian' or 'traditional'
203
     * @return string
204
     */
205 11
    public function localDate($date, $format = null, $calendar = 'gregorian')
206
    {
207 11
        return $this->formatLocal($date, $format, false, $calendar);
208
    }
209
210
    /**
211
     * Format the time value as a string based on the current locale
212
     *
213
     * @param DateTime|int|string $date
214
     * @param string              $format    'short', 'medium', 'long', 'full' or pattern
215
     * @param string              $calendar  'gregorian' or 'traditional'
216
     * @return string
217
     */
218 7
    public function localTime($date, $format = 'short', $calendar = 'gregorian')
219
    {
220 7
        return $this->formatLocal($date, false, $format, $calendar);
221
    }
222
223
    /**
224
     * Format the date/time value as a string based on the current locale
225
     *
226
     * @param DateTime|int|string $date
227
     * @param string              $format    date format, pattern or ['date'=>format, 'time'=>format)
228
     * @param string              $calendar  'gregorian' or 'traditional'
229
     * @return string
230
     */
231 5
    public function localDateTime($date, $format = null, $calendar = 'gregorian')
232
    {
233 5
        if (is_array($format) || $format instanceof \stdClass || !isset($format)) {
234 2
            $formatDate = isset($format['date']) ? $format['date'] : null;
235 2
            $formatTime = isset($format['time']) ? $format['time'] : 'short';
236
        } else {
237 3
            $formatDate = $format;
238 3
            $formatTime = false;
239
        }
240
241 5
        return $this->formatLocal($date, $formatDate, $formatTime, $calendar);
242
    }
243
244
245
    /**
246
     * Split duration into seconds, minutes, hours, days, weeks and years.
247
     *
248
     * @param int $seconds
249
     * @return array
250
     */
251 13
    protected function splitDuration($seconds, $max)
252
    {
253 13
        if ($max < 1 || $seconds < 60) {
254 1
            return [$seconds];
255
        }
256
257 12
        $minutes = floor($seconds / 60);
258 12
        $seconds = $seconds % 60;
259 12
        if ($max < 2 || $minutes < 60) {
260 2
            return [$seconds, $minutes];
261
        }
262
263 10
        $hours = floor($minutes / 60);
264 10
        $minutes = $minutes % 60;
265 10
        if ($max < 3 || $hours < 24) {
266 4
            return [$seconds, $minutes, $hours];
267
        }
268
269 6
        $days = floor($hours / 24);
270 6
        $hours = $hours % 24;
271 6
        if ($max < 4 || $days < 7) {
272 2
            return [$seconds, $minutes, $hours, $days];
273
        }
274
275 4
        $weeks = floor($days / 7);
276 4
        $days = $days % 7;
277 4
        if ($max < 5 || $weeks < 52) {
278 2
            return [$seconds, $minutes, $hours, $days, $weeks];
279
        }
280
281 2
        $years = floor($weeks / 52);
282 2
        $weeks = $weeks % 52;
283 2
        return [$seconds, $minutes, $hours, $days, $weeks, $years];
284
    }
285
286
    /**
287
     * Calculate duration from seconds.
288
     * One year is seen as exactly 52 weeks.
289
     *
290
     * Use null to skip a unit.
291
     *
292
     * @param int    $value     Time in seconds
293
     * @param array  $units     Time units (seconds, minutes, hours, days, weeks, years)
294
     * @param string $separator
295
     * @return string
296
     */
297 14
    public function duration($value, $units = ['s', 'm', 'h', 'd', 'w', 'y'], $separator = ' ')
298
    {
299 14
        if (!isset($value)) {
300 1
            return null;
301
        }
302
303 13
        $parts = $this->splitDuration($value, count($units) - 1) + array_fill(0, 6, null);
304
305 13
        $duration = '';
306
307 13
        for ($i = 5; $i >= 0; $i--) {
308 13
            if (isset($parts[$i]) && isset($units[$i])) {
309 13
                $duration .= $separator . $parts[$i] . $units[$i];
310
            }
311
        }
312
313 13
        return trim($duration, $separator);
314
    }
315
316
    /**
317
     * Get the age (in years) based on a date.
318
     *
319
     * @param DateTime|string $value
320
     * @return int
321
     */
322 3
    public function age($value)
323
    {
324 3
        if (!isset($value)) {
325 1
            return null;
326
        }
327
328 2
        $date = $this->valueToDateTime($value);
329
330 2
        return $date->diff(new \DateTime())->format('%y');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $date->diff(new DateTime())->format('%y') returns the type string which is incompatible with the documented return type integer.
Loading history...
331
    }
332
}
333