Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
8.33% |
8 / 96 |
|
11.11% |
1 / 9 |
CRAP | |
0.00% |
0 / 1 |
| tao_helpers_Date | |
8.33% |
8 / 96 |
|
11.11% |
1 / 9 |
2300.06 | |
0.00% |
0 / 1 |
| getDateFormatter | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
| displayeDate | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
110 | |||
| displayInterval | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
72 | |||
| formatElapsed | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
| getNonNullIntervalFormats | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
132 | |||
| getTimeStamp | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| getTimeStampWithMicroseconds | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getTimeKeys | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
132 | |||
| formatMicrotime | |
100.00% |
8 / 8 |
|
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 | |
| 25 | use oat\tao\helpers\dateFormatter\EuropeanFormatter; |
| 26 | use 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 | */ |
| 35 | class 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 | } |