Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.33% covered (success)
93.33%
42 / 45
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
datetime
93.33% covered (success)
93.33%
42 / 45
25.00% covered (danger)
25.00%
1 / 4
25.19
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 format
96.43% covered (success)
96.43%
27 / 28
0.00% covered (danger)
0.00%
0 / 1
15
 __toString
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 format_cache
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
7.02
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14namespace phpbb;
15
16/**
17* phpBB custom extensions to the PHP DateTime class
18* This handles the relative formats phpBB employs
19*/
20class datetime extends \DateTime
21{
22    /**
23    * String used to wrap the date segment which should be replaced by today/tomorrow/yesterday
24    */
25    const RELATIVE_WRAPPER = '|';
26
27    /**
28    * @var user User who is the context for this DateTime instance
29    */
30    protected $user;
31
32    /**
33    * @var array Date formats are preprocessed by phpBB, to save constant recalculation they are cached.
34    */
35    protected static $format_cache = array();
36
37    /**
38    * Constructs a new instance of \phpbb\datetime, expanded to include an argument to inject
39    * the user context and modify the timezone to the users selected timezone if one is not set.
40    *
41    * @param user $user object for context.
42    * @param string $time String in a format accepted by strtotime().
43    * @param \DateTimeZone|null $timezone Time zone of the time.
44    */
45    public function __construct($user, $time = 'now', \DateTimeZone $timezone = null)
46    {
47        $this->user    = $user;
48        $timezone    = $timezone ?: $this->user->timezone;
49
50        parent::__construct($time, $timezone);
51    }
52
53    /**
54    * Formats the current date time into the specified format
55    *
56    * @param string $format Optional format to use for output, defaults to users chosen format
57    * @param boolean $force_absolute Force output of a non relative date
58    * @return string Formatted date time
59    */
60    #[\ReturnTypeWillChange]
61    public function format($format = '', $force_absolute = false)
62    {
63        $format        = $format ? $format : $this->user->date_format;
64
65        if (substr($this->user->lang_name, 0,2) != 'en')
66        {
67            $format = preg_replace('/([^\\\])S/','$1', $format);
68        }
69
70        $format        = self::format_cache($format, $this->user);
71        $relative    = ($format['is_short'] && !$force_absolute);
72        $now        = new self($this->user, 'now', $this->user->timezone);
73
74        $timestamp    = $this->getTimestamp();
75        $now_ts        = $now->getTimeStamp();
76
77        $delta        = $now_ts - $timestamp;
78
79        if ($relative)
80        {
81            /*
82            * Check the delta is less than or equal to 1 hour
83            * and the delta not more than a minute in the past
84            * and the delta is either greater than -5 seconds or timestamp
85            * and current time are of the same minute (they must be in the same hour already)
86            * finally check that relative dates are supported by the language pack
87            */
88            if ($delta <= 3600 && $delta > -60 &&
89                ($delta >= -5 || (($now_ts / 60) % 60) == (($timestamp / 60) % 60))
90                && isset($this->user->lang['datetime']['AGO']))
91            {
92                return $this->user->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60)));
93            }
94            else
95            {
96                $midnight = clone $now;
97                $midnight->setTime(0, 0, 0);
98
99                $midnight    = $midnight->getTimestamp();
100
101                if ($timestamp < $midnight + 2 * 86400)
102                {
103                    $day = false;
104
105                    if ($timestamp >= $midnight + 86400)
106                    {
107                        $day = 'TOMORROW';
108                    }
109                    else if ($timestamp >= $midnight)
110                    {
111                        $day = 'TODAY';
112                    }
113                    else if ($timestamp >= $midnight - 86400)
114                    {
115                        $day = 'YESTERDAY';
116                    }
117
118                    if ($day !== false)
119                    {
120                        // Format using the short formatting and finally swap out the relative token placeholder with the correct value
121                        return str_replace(self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER, $this->user->lang['datetime'][$day], strtr(parent::format($format['format_short']), $format['lang']));
122                    }
123                }
124            }
125        }
126
127        return strtr(parent::format($format['format_long']), $format['lang']);
128    }
129
130    /**
131    * Magic method to convert DateTime object to string
132    *
133    * @return string Formatted date time, according to the users default settings.
134    */
135    public function __toString()
136    {
137        return $this->format();
138    }
139
140    /**
141    * Pre-processes the specified date format
142    *
143    * @param string $format Output format
144    * @param user $user User object to use for localisation
145    * @return array Processed date format
146    */
147    protected static function format_cache($format, $user)
148    {
149        $lang = $user->lang_name;
150
151        if (!isset(self::$format_cache[$lang]))
152        {
153            self::$format_cache[$lang] = array();
154        }
155
156        if (!isset(self::$format_cache[$lang][$format]))
157        {
158            // Is the user requesting a friendly date format (i.e. 'Today 12:42')?
159            self::$format_cache[$lang][$format] = array(
160                'is_short'        => strpos($format, self::RELATIVE_WRAPPER) !== false,
161                'format_short'    => substr($format, 0, strpos($format, self::RELATIVE_WRAPPER)) . self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER . substr(strrchr($format, self::RELATIVE_WRAPPER), 1),
162                'format_long'    => str_replace(self::RELATIVE_WRAPPER, '', $format),
163                'lang'            => array_filter($user->lang['datetime'], 'is_string'),
164            );
165
166            // Short representation of month in format? Some languages use different terms for the long and short format of May
167            if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false))
168            {
169                self::$format_cache[$lang][$format]['lang']['May'] = $user->lang['datetime']['May_short'];
170            }
171        }
172
173        return self::$format_cache[$lang][$format];
174    }
175}