Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
8.33% covered (danger)
8.33%
8 / 96
11.11% covered (danger)
11.11%
1 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
tao_helpers_Date
8.33% covered (danger)
8.33%
8 / 96
11.11% covered (danger)
11.11%
1 / 9
2300.06
0.00% covered (danger)
0.00%
0 / 1
 getDateFormatter
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 displayeDate
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
110
 displayInterval
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
72
 formatElapsed
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 getNonNullIntervalFormats
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
132
 getTimeStamp
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 getTimeStampWithMicroseconds
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTimeKeys
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
132
 formatMicrotime
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2008-2010 (original work) Deutsche Institut für Internationale Pädagogische Forschung
19 *                         (under the project TAO-TRANSFER);
20 *               2009-2012 (update and modification) Public Research Centre Henri Tudor
21 *                         (under the project TAO-SUSTAIN & TAO-DEV);
22 *
23 */
24
25use oat\tao\helpers\dateFormatter\EuropeanFormatter;
26use oat\tao\helpers\dateFormatter\DateFormatterInterface;
27
28/**
29 * Utility to display dates.
30 *
31 * @author Joel Bout, <joel@taotesting.com>
32 * @package tao
33 *
34 */
35class tao_helpers_Date
36{
37    public const CONFIG_KEY = 'dateService';
38
39    public const FORMAT_LONG = 0;
40
41    public const FORMAT_VERBOSE = 1;
42
43    public const FORMAT_DATEPICKER = 2;
44
45    public const FORMAT_ISO8601 = 3;
46
47    public const FORMAT_LONG_MICROSECONDS = 4;
48
49    public const FORMAT_INTERVAL_LONG = 100;
50
51    public const FORMAT_INTERVAL_SHORT = 101;
52
53    public const FORMAT_FALLBACK = -1;
54
55    private static $service;
56
57    /**
58     * Returns configured date formatter.
59     *
60     * @return DateFormatterInterface
61     */
62    public static function getDateFormatter()
63    {
64        if (is_null(self::$service)) {
65            $ext = common_ext_ExtensionsManager::singleton()->getExtensionById('tao');
66            $service = $ext->getConfig(self::CONFIG_KEY);
67            self::$service = $service instanceof DateFormatterInterface
68                ? $service
69                : new EuropeanFormatter();
70        }
71
72        return self::$service;
73    }
74
75    /**
76     * Displays a date/time
77     * Should in theory be dependant on the users locale and timezone
78     * @param mixed $timestamp
79     * @param int $format The date format. See tao_helpers_Date's constants.
80     * @param DateTimeZone $timeZone user timezone
81     * @return string The formatted date.
82     * @throws common_Exception when timestamp is not recognized
83     */
84    public static function displayeDate($timestamp, $format = self::FORMAT_LONG, DateTimeZone $timeZone = null)
85    {
86        if (is_object($timestamp) && $timestamp instanceof core_kernel_classes_Literal) {
87            $ts = $timestamp->__toString();
88        } elseif (is_object($timestamp) && $timestamp instanceof DateTimeInterface) {
89            $ts = self::getTimeStampWithMicroseconds($timestamp);
90        } elseif (is_numeric($timestamp)) {
91            $ts = $timestamp;
92        } elseif (is_string($timestamp) && preg_match('/.\+0000$/', $timestamp)) {
93            $ts = self::getTimeStampWithMicroseconds(new DateTime($timestamp, new DateTimeZone('UTC')));
94        } elseif (is_string($timestamp) && preg_match('/0\.[\d]+\s[\d]+/', $timestamp)) {
95            $ts = self::getTimeStamp($timestamp, true);
96        } else {
97            throw new common_Exception('Unexpected timestamp');
98        }
99
100        return self::getDateFormatter()->format($ts, $format, $timeZone);
101    }
102
103    /**
104     *
105     * @author Lionel Lecaque, lionel@taotesting.com
106     * @param unknown $interval
107     * @param integer $format
108     * @return string|Ambigous <string, string>
109     */
110    public static function displayInterval($interval, $format = self::FORMAT_INTERVAL_LONG)
111    {
112        if (is_object($interval)) {
113            $intervalObj = $interval;
114        } else {
115            $intervalObj = new DateTime();
116            $intervalObj->setTimestamp($interval);
117        }
118        $newDate = new \DateTime();
119        $intervalObj = $intervalObj instanceof DateTimeInterface ? $newDate->diff($intervalObj, true) : $intervalObj;
120        if (! $intervalObj instanceof DateInterval) {
121            common_Logger::w('Unknown interval format ' . get_class($interval) . ' for ' . __FUNCTION__);
122            return '';
123        }
124
125        $formatStrings = self::getNonNullIntervalFormats($intervalObj);
126        if (empty($formatStrings)) {
127            $returnValue = __("less than a minute");
128        } else {
129            $returnValue = '';
130            switch ($format) {
131                case self::FORMAT_INTERVAL_SHORT:
132                    $returnValue = $intervalObj->format(array_shift($formatStrings));
133                    break;
134                case self::FORMAT_INTERVAL_LONG:
135                    $returnValue = self::formatElapsed($intervalObj, $formatStrings);
136                    break;
137                default:
138                    common_Logger::w('Unknown date format ' . $format . ' for ' . __FUNCTION__);
139            }
140        }
141        return $returnValue;
142    }
143
144    /**
145     *
146     * @author Lionel Lecaque, lionel@taotesting.com
147     * @param DateInterval $interval
148     * @param unknown $formatStrings
149     * @return string
150     */
151    protected static function formatElapsed(DateInterval $interval, $formatStrings)
152    {
153        $string = '';
154        while (! empty($formatStrings)) {
155            $string .= $interval->format(array_shift($formatStrings))
156                . (count($formatStrings) == 0 ? '' : (count($formatStrings) == 1 ? __(' and ') : ' '));
157        }
158        return $string;
159    }
160
161    /**
162     *
163     * @author Lionel Lecaque, lionel@taotesting.com
164     * @param DateInterval $interval
165     * @return multitype:string Ambigous <string, string>
166     */
167    private static function getNonNullIntervalFormats(DateInterval $interval)
168    {
169        $formats = [];
170        if ($interval->y > 0) {
171            $formats[] = $interval->y == 1 ? __("%y year") : __("%y years");
172        }
173        if ($interval->m > 0) {
174            $formats[] = $interval->m == 1 ? __("%m month") : __("%m months");
175        }
176        if ($interval->d > 0) {
177            $formats[] = $interval->d == 1 ? __("%d day") : __("%d days");
178        }
179        if ($interval->h > 0) {
180            $formats[] = $interval->h == 1 ? __("%h hour") : __("%h hours");
181        }
182        if ($interval->i > 0) {
183            $formats[] = $interval->i == 1 ? __("%i minute") : __("%i minutes");
184        }
185        return $formats;
186    }
187
188    /**
189     *
190     * @author Lionel Lecaque, lionel@taotesting.com
191     * @param unknown $microtime
192     * @return number
193     */
194    public static function getTimeStamp($microtime, $microseconds = false)
195    {
196        $parts = array_reverse(explode(" ", $microtime));
197
198        if ($microseconds && isset($parts[1])) {
199            $round = sprintf('%0.6f', $parts[1]);
200            if ($round === '1.000000') {
201                // Edge case -> rounded up to the second.
202                $timestamp = '' . (intval($parts[0]) + 1) . '.000000';
203            } else {
204                $timestamp = $parts[0] . '.' . str_replace('0.', '', $round);
205            }
206        } else {
207            $timestamp = $parts[0];
208        }
209
210        return $timestamp;
211    }
212
213    public static function getTimeStampWithMicroseconds(DateTime $dt)
214    {
215        return join('.', [$dt->getTimestamp(), $dt->format('u')]);
216    }
217
218    /**
219     * Get array of DateTime objects build from $date (or current time if not given) $amount times back with given
220     * interval
221     * Example:
222     * $timeKeys = $service->getTimeKeys(new \DateInterval('PT1H'), new \DateTime('now'), 24);
223     *
224     *   array (
225     *     0 =>
226     *       DateTime::__set_state(array(
227     *       'date' => '2017-04-24 08:00:00.000000',
228     *       'timezone_type' => 1,
229     *       'timezone' => '+00:00',
230     *     )),
231     *     1 =>
232     *       DateTime::__set_state(array(
233     *       'date' => '2017-04-24 07:00:00.000000',
234     *       'timezone_type' => 1,
235     *       'timezone' => '+00:00',
236     *     )),
237     *     2 =>
238     *       DateTime::__set_state(array(
239     *       'date' => '2017-04-24 06:00:00.000000',
240     *       'timezone_type' => 1,
241     *       'timezone' => '+00:00',
242     *     )),
243     *       ...
244     *   )
245     *
246     * @param \DateInterval $interval
247     * @param \DateTimeInterface|null $date
248     * @param null $amount
249     * @return \DateTime[]
250     */
251    public static function getTimeKeys(\DateInterval $interval, \DateTimeInterface $date = null, $amount = null)
252    {
253        $timeKeys = [];
254        if ($date === null) {
255            $date = new \DateTime('now', new \DateTimeZone('UTC'));
256        }
257
258        if ($interval->format('%i') > 0) {
259            $date->setTime($date->format('H'), $date->format('i') + 1, 0);
260            $amount = $amount === null ? 60 : $amount;
261        }
262        if ($interval->format('%h') > 0) {
263            $date->setTime($date->format('H') + 1, 0, 0);
264            $amount = $amount === null ? 24 : $amount;
265        }
266        if ($interval->format('%d') > 0) {
267            $date->setTime(0, 0, 0);
268            $date->setDate($date->format('Y'), $date->format('m'), $date->format('d') + 1);
269            $amount = $amount === null
270                ? cal_days_in_month(CAL_GREGORIAN, $date->format('m'), $date->format('Y'))
271                : $amount;
272        }
273        if ($interval->format('%m') > 0) {
274            $date->setTime(0, 0, 0);
275            $date->setDate($date->format('Y'), $date->format('m') + 1, 1);
276            $amount = $amount === null ? 12 : $amount;
277        }
278
279        while ($amount > 0) {
280            $timeKeys[] = new \DateTime($date->format(\DateTime::ISO8601), new \DateTimeZone('UTC'));
281            $date->sub($interval);
282            $amount--;
283        }
284        return $timeKeys;
285    }
286
287    /**
288     * Converts from microseconds seconds format of microtime() function to RFC3339_EXTENDED
289     * @param string|null $microtime
290     * @return string|null
291     * @example 0.47950700 1700135696 to 1700135696.47950700
292     */
293    public static function formatMicrotime(?string $microtime): ?string
294    {
295        if ($microtime === null) {
296            return null;
297        }
298
299        // Split the string into microseconds and seconds
300        list($microseconds, $seconds) = explode(' ', $microtime);
301
302        // Show only the numbers after the dot without the integral part
303        list(, $decimalPart) = explode('.', sprintf('%0.6f', $microseconds));
304        //To preserve time zone we are not using createFromFormat()
305        $date = new DateTime();
306        $date->setTimestamp((int)$seconds);
307        $date->modify('+ ' . $decimalPart . ' microseconds');
308
309        return $date->format(DateTimeInterface::RFC3339_EXTENDED);
310    }
311}