Time.php 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349
  1. <?php
  2. namespace Tools\Utility;
  3. use Cake\Chronos\MutableDate;
  4. use Cake\Core\Configure;
  5. use Cake\I18n\Date;
  6. use Cake\I18n\Time as CakeTime;
  7. use DateInterval;
  8. use DateTime;
  9. /**
  10. * Extend CakeTime with a few important improvements:
  11. * - correct timezones for date only input and therefore unchanged day here
  12. */
  13. class Time extends CakeTime {
  14. /**
  15. * {@inheritDoc}
  16. */
  17. public function __construct($time = null, $tz = null) {
  18. if (is_array($time)) {
  19. $value = $time + ['hour' => 0, 'minute' => 0, 'second' => 0];
  20. $format = '';
  21. if (
  22. isset($value['year'], $value['month'], $value['day']) &&
  23. (is_numeric($value['year']) && is_numeric($value['month']) && is_numeric($value['day']))
  24. ) {
  25. $format .= sprintf('%d-%02d-%02d', $value['year'], $value['month'], $value['day']);
  26. }
  27. if (isset($value['meridian'])) {
  28. $value['hour'] = strtolower($value['meridian']) === 'am' ? $value['hour'] : $value['hour'] + 12;
  29. }
  30. $format .= sprintf(
  31. '%s%02d:%02d:%02d',
  32. empty($format) ? '' : ' ',
  33. $value['hour'],
  34. $value['minute'],
  35. $value['second']
  36. );
  37. $time = $format;
  38. }
  39. parent::__construct($time, $tz);
  40. }
  41. /**
  42. * Detect if a timezone has a DST
  43. *
  44. * @param string|\DateTimeZone|null $timezone User's timezone string or DateTimeZone object
  45. * @return bool
  46. */
  47. public function hasDaylightSavingTime($timezone = null) {
  48. $timezone = $this->safeCreateDateTimeZone($timezone);
  49. // a date outside of DST
  50. $offset = $timezone->getOffset(new CakeTime('@' . mktime(0, 0, 0, 2, 1, date('Y'))));
  51. $offset = $offset / HOUR;
  52. // a date inside of DST
  53. $offset2 = $timezone->getOffset(new CakeTime('@' . mktime(0, 0, 0, 8, 1, date('Y'))));
  54. $offset2 = $offset2 / HOUR;
  55. return abs($offset2 - $offset) > 0;
  56. }
  57. /**
  58. * Calculate the difference between two dates
  59. *
  60. * TODO: deprecate in favor of DateTime::diff() etc which will be more precise
  61. *
  62. * should only be used for < month (due to the different month lenghts it gets fuzzy)
  63. *
  64. * @param mixed $startTime (db format or timestamp)
  65. * @param mixed|null $endTime (db format or timestamp)
  66. * @param array $options
  67. * @return int The distance in seconds
  68. */
  69. public static function difference($startTime, $endTime = null, array $options = []) {
  70. if (!is_int($startTime)) {
  71. $startTime = strtotime($startTime);
  72. }
  73. if (!is_int($endTime)) {
  74. $endTime = strtotime($endTime);
  75. }
  76. //@FIXME: make it work for > month
  77. return abs($endTime - $startTime);
  78. }
  79. /**
  80. * Calculates the age using start and optional end date.
  81. * Both dates default to current date. Note that start needs
  82. * to be before end for a valid result.
  83. *
  84. * @param int|string|\DateTimeInterface $start Start date (if empty, use today)
  85. * @param int|string|\DateTimeInterface|null $end End date (if empty, use today)
  86. * @return int Age (0 if both timestamps are equal or empty, -1 on invalid dates)
  87. */
  88. public static function age($start, $end = null) {
  89. if (empty($start) && empty($end) || $start == $end) {
  90. return 0;
  91. }
  92. if (is_int($start)) {
  93. $start = date(FORMAT_DB_DATE, $start);
  94. }
  95. if (is_int($end)) {
  96. $end = date(FORMAT_DB_DATE, $end);
  97. }
  98. $startDate = $start;
  99. if (!is_object($start)) {
  100. $startDate = new CakeTime($start);
  101. }
  102. $endDate = $end;
  103. if (!is_object($end)) {
  104. $endDate = new CakeTime($end);
  105. }
  106. if ($startDate > $endDate) {
  107. return -1;
  108. }
  109. $oDateInterval = $endDate->diff($startDate);
  110. return $oDateInterval->y;
  111. }
  112. /**
  113. * Returns the age only with the year available
  114. * can be e.g. 22/23
  115. *
  116. * @param int $year
  117. * @param int|null $month (optional)
  118. * @return int|string Age
  119. */
  120. public static function ageByYear($year, $month = null) {
  121. if ($month === null) {
  122. $maxAge = static::age(mktime(0, 0, 0, 1, 1, $year));
  123. $minAge = static::age(mktime(23, 59, 59, 12, 31, $year));
  124. $ages = array_unique([$minAge, $maxAge]);
  125. return implode('/', $ages);
  126. }
  127. if (date('n') == $month) {
  128. $maxAge = static::age(mktime(0, 0, 0, $month, 1, $year));
  129. $minAge = static::age(mktime(23, 59, 59, $month, static::daysInMonth($year, $month), $year));
  130. $ages = array_unique([$minAge, $maxAge]);
  131. return implode('/', $ages);
  132. }
  133. return static::age(mktime(0, 0, 0, $month, 1, $year));
  134. }
  135. /**
  136. * Rounded age depended on steps (e.g. age 16 with steps = 10 => "11-20")
  137. * //FIXME
  138. * //TODO: move to helper?
  139. *
  140. * @param int $year
  141. * @param int|null $month
  142. * @param int|null $day
  143. * @param int $steps
  144. * @return mixed
  145. */
  146. public static function ageRange($year, $month = null, $day = null, $steps = 1) {
  147. if ($month == null && $day == null) {
  148. $age = (int)date('Y') - $year - 1;
  149. } elseif ($day == null) {
  150. if ($month >= (int)date('m')) {
  151. $age = (int)date('Y') - $year - 1;
  152. } else {
  153. $age = (int)date('Y') - $year;
  154. }
  155. } else {
  156. if ($month > (int)date('m') || ($month == (int)date('m') && $day > (int)date('d'))) {
  157. $age = (int)date('Y') - $year - 1;
  158. } else {
  159. $age = (int)date('Y') - $year;
  160. }
  161. }
  162. if ($age % $steps == 0) {
  163. $lowerRange = $age - $steps + 1;
  164. $upperRange = $age;
  165. } else {
  166. $lowerRange = $age - ($age % $steps) + 1;
  167. $upperRange = $age - ($age % $steps) + $steps;
  168. }
  169. if ($lowerRange == $upperRange) {
  170. return $upperRange;
  171. }
  172. return [$lowerRange, $upperRange];
  173. }
  174. /**
  175. * Return the days of a given month.
  176. *
  177. * @param int $year
  178. * @param int $month
  179. * @return string Days
  180. */
  181. public static function daysInMonth($year, $month) {
  182. return date('t', mktime(0, 0, 0, $month, 1, $year));
  183. }
  184. /**
  185. * Calendar Week (current week of the year).
  186. * //TODO: use timestamp - or make the function able to use timestamps at least (besides dateString)
  187. *
  188. * date('W', $time) returns ISO6801 week number.
  189. * Exception: Dates of the calender week of the previous year return 0. In this case the cweek of the
  190. * last week of the previous year should be used.
  191. *
  192. * @param mixed|null $dateString In DB format - if none is passed, current day is used
  193. * @param int $relative - weeks relative to the date (+1 next, -1 previous etc)
  194. * @return string
  195. */
  196. public static function cWeek($dateString = null, $relative = 0) {
  197. //$time = self::fromString($dateString);
  198. if ($dateString) {
  199. $date = explode(' ', $dateString);
  200. list($y, $m, $d) = explode('-', $date[0]);
  201. $t = mktime(0, 0, 0, $m, $d, $y);
  202. } else {
  203. $y = date('Y');
  204. $t = time();
  205. }
  206. $relative = (int)$relative;
  207. if ($relative != 0) {
  208. $t += WEEK * $relative; // 1day * 7 * relativeWeeks
  209. }
  210. $kw = (int)date('W', $t);
  211. if ($kw === 0) {
  212. $kw = 1 + (int)date($t - DAY * (int)date('w', $t), 'W');
  213. $y--;
  214. }
  215. return str_pad($kw, 2, '0', STR_PAD_LEFT) . '/' . $y;
  216. }
  217. /**
  218. * Get number of days since the start of the week.
  219. * 0=sunday to 7=saturday (default)
  220. *
  221. * @param int $num Number of day.
  222. * @return int Days since the start of the week.
  223. */
  224. public static function cWeekMod($num) {
  225. $base = 6;
  226. return (int)($num - $base * floor($num / $base));
  227. }
  228. /**
  229. * Calculate the beginning of a calender week
  230. * if no calendar week is given get the beginning of the first week of the year
  231. *
  232. * @param int $year (format xxxx)
  233. * @param int $cWeek (optional, defaults to first, range 1...52/53)
  234. * @return int Timestamp
  235. */
  236. public static function cWeekBeginning($year, $cWeek = 0) {
  237. if ($cWeek <= 1 || $cWeek > static::cWeeks($year)) {
  238. $first = mktime(0, 0, 0, 1, 1, $year);
  239. $wtag = (int)date('w', $first);
  240. if ($wtag <= 4) {
  241. /* Thursday or less: back to Monday */
  242. $firstmonday = mktime(0, 0, 0, 1, 1 - ($wtag - 1), $year);
  243. } elseif ($wtag != 1) {
  244. /* Back to Monday */
  245. $firstmonday = mktime(0, 0, 0, 1, 1 + (7 - $wtag + 1), $year);
  246. } else {
  247. $firstmonday = $first;
  248. }
  249. return $firstmonday;
  250. }
  251. $monday = strtotime($year . 'W' . static::pad($cWeek) . '1');
  252. return $monday;
  253. }
  254. /**
  255. * Calculate the ending of a calenderweek
  256. * if no cweek is given get the ending of the last week of the year
  257. *
  258. * @param int $year (format xxxx)
  259. * @param int $cWeek (optional, defaults to last, range 1...52/53)
  260. * @return int Timestamp
  261. */
  262. public static function cWeekEnding($year, $cWeek = 0) {
  263. if ($cWeek < 1 || $cWeek >= static::cWeeks($year)) {
  264. return static::cWeekBeginning($year + 1) - 1;
  265. }
  266. return static::cWeekBeginning($year, $cWeek + 1) - 1;
  267. }
  268. /**
  269. * Calculate the amount of calender weeks in a year
  270. *
  271. * @param int|null $year (format xxxx, defaults to current year)
  272. * @return int Amount of weeks - 52 or 53
  273. */
  274. public static function cWeeks($year = null) {
  275. if ($year === null) {
  276. $year = date('Y');
  277. }
  278. return (int)date('W', mktime(23, 59, 59, 12, 28, $year));
  279. }
  280. /**
  281. * Handles month/year increment calculations in a safe way, avoiding the pitfall of "fuzzy" month units.
  282. *
  283. * @param mixed $startDate Either a date string or a DateTime object
  284. * @param int $years Years to increment/decrement
  285. * @param int $months Months to increment/decrement
  286. * @param int $days Days
  287. * @param string|\DateTimeZone|int|null $timezone Timezone string or DateTimeZone object
  288. * @return object DateTime with incremented/decremented month/year values.
  289. */
  290. public function incrementDate($startDate, $years = 0, $months = 0, $days = 0, $timezone = null) {
  291. $dateTime = $startDate;
  292. if (!is_object($startDate)) {
  293. $dateTime = new CakeTime($startDate);
  294. if ($timezone) {
  295. $dateTime->setTimezone($this->safeCreateDateTimeZone($timezone));
  296. }
  297. }
  298. $startingTimeStamp = $dateTime->getTimestamp();
  299. // Get the month value of the given date:
  300. $monthString = date('Y-m', $startingTimeStamp);
  301. // Create a date string corresponding to the 1st of the give month,
  302. // making it safe for monthly/yearly calculations:
  303. $safeDateString = "first day of $monthString";
  304. // offset is wrong
  305. $months++;
  306. // Increment date by given month/year increments:
  307. $incrementedDateString = "$safeDateString $months month $years year";
  308. $newTimeStamp = strtotime($incrementedDateString) + $days * DAY;
  309. $newDate = DateTime::createFromFormat('U', $newTimeStamp);
  310. return $newDate;
  311. }
  312. /**
  313. * Get the age bounds (min, max) as timestamp that would result in the given age(s)
  314. * note: expects valid age (> 0 and < 120)
  315. *
  316. * @param int $firstAge
  317. * @param int|null $secondAge (defaults to first one if not specified)
  318. * @param bool $returnAsString
  319. * @param string|null $relativeTime
  320. * @return array Array('min'=>$min, 'max'=>$max);
  321. */
  322. public static function ageBounds($firstAge, $secondAge = null, $returnAsString = false, $relativeTime = null) {
  323. if ($secondAge === null) {
  324. $secondAge = $firstAge;
  325. }
  326. //TODO: other relative time then today should work as well
  327. $Date = new CakeTime($relativeTime !== null ? $relativeTime : 'now');
  328. $max = mktime(23, 23, 59, $Date->format('m'), $Date->format('d'), (int)$Date->format('Y') - $firstAge);
  329. $min = mktime(0, 0, 1, $Date->format('m'), (int)$Date->format('d') + 1, (int)$Date->format('Y') - $secondAge - 1);
  330. if ($returnAsString) {
  331. $max = date(FORMAT_DB_DATE, $max);
  332. $min = date(FORMAT_DB_DATE, $min);
  333. }
  334. return ['min' => $min, 'max' => $max];
  335. }
  336. /**
  337. * For birthdays etc
  338. *
  339. * @param string $dateString
  340. * @param int $seconds
  341. * @return bool Success
  342. */
  343. public static function isInRange($dateString, $seconds) {
  344. $newDate = time();
  345. return static::difference($dateString, $newDate) <= $seconds;
  346. }
  347. /**
  348. * Outputs Date(time) Sting nicely formatted (+ localized!)
  349. *
  350. * Options:
  351. * - timezone: User's timezone
  352. * - default: Default string (defaults to "-----")
  353. * - oclock: Set to true to append oclock string
  354. *
  355. * @param string|null $dateString
  356. * @param string|null $format Format (YYYY-MM-DD, DD.MM.YYYY)
  357. * @param array $options
  358. * @return string
  359. */
  360. public static function localDate($dateString, $format = null, array $options = []) {
  361. $defaults = ['default' => '-----', 'timezone' => null];
  362. $options += $defaults;
  363. if ($options['timezone'] === null && strlen($dateString) === 10) {
  364. $options['timezone'] = static::_getDefaultOutputTimezone();
  365. }
  366. if ($dateString === null) {
  367. $dateString = time();
  368. }
  369. if ($options['timezone']) {
  370. $options['timezone'] = static::safeCreateDateTimeZone($options['timezone']);
  371. }
  372. $date = new CakeTime($dateString, $options['timezone']);
  373. $date = $date->format('U');
  374. if ($date === null || $date === false || $date <= 0) {
  375. return $options['default'];
  376. }
  377. if ($format === null) {
  378. if (is_int($dateString) || strpos($dateString, ' ') !== false) {
  379. $format = FORMAT_LOCAL_YMDHM;
  380. } else {
  381. $format = FORMAT_LOCAL_YMD;
  382. }
  383. }
  384. $date = static::_strftime($format, $date);
  385. if (!empty($options['oclock'])) {
  386. switch ($format) {
  387. case FORMAT_LOCAL_YMDHM:
  388. case FORMAT_LOCAL_YMDHMS:
  389. case FORMAT_LOCAL_HM:
  390. case FORMAT_LOCAL_HMS:
  391. $date .= ' ' . __d('tools', 'o\'clock');
  392. break;
  393. }
  394. }
  395. return $date;
  396. }
  397. /**
  398. * Multibyte wrapper for strftime.
  399. *
  400. * Handles utf8_encoding the result of strftime when necessary.
  401. *
  402. * @param string $format Format string.
  403. * @param int $date Timestamp to format.
  404. * @return string formatted string with correct encoding.
  405. */
  406. protected static function _strftime($format, $date) {
  407. $format = strftime($format, $date);
  408. $encoding = Configure::read('App.encoding');
  409. if (!empty($encoding) && $encoding === 'UTF-8') {
  410. $valid = mb_check_encoding($format, $encoding);
  411. if (!$valid) {
  412. $format = utf8_encode($format);
  413. }
  414. }
  415. return $format;
  416. }
  417. /**
  418. * Outputs Date(time) Sting nicely formatted
  419. *
  420. * Options:
  421. * - timezone: User's timezone
  422. * - default: Default string (defaults to "-----")
  423. * - oclock: Set to true to append oclock string
  424. *
  425. * @param string|\DateTimeInterface|null $dateString
  426. * @param string|null $format Format (YYYY-MM-DD, DD.MM.YYYY)
  427. * @param array $options Options
  428. * @return string
  429. */
  430. public static function niceDate($dateString, $format = null, array $options = []) {
  431. $defaults = ['default' => '-----', 'timezone' => null];
  432. $options += $defaults;
  433. if ($options['timezone'] === null) {
  434. $options['timezone'] = static::_getDefaultOutputTimezone();
  435. }
  436. if ($options['timezone']) {
  437. $options['timezone'] = static::safeCreateDateTimeZone($options['timezone']);
  438. }
  439. if ($dateString === null) {
  440. return $options['default'];
  441. }
  442. if (!is_object($dateString)) {
  443. if (strlen($dateString) === 10) {
  444. $date = new Date($dateString);
  445. } else {
  446. $date = new CakeTime($dateString);
  447. }
  448. } else {
  449. $date = $dateString;
  450. }
  451. if ($date === null) {
  452. return $options['default'];
  453. }
  454. if ($format === null) {
  455. if ($date instanceof MutableDate) {
  456. $format = FORMAT_NICE_YMD;
  457. } else {
  458. $format = FORMAT_NICE_YMDHM;
  459. }
  460. }
  461. $date = $date->timezone($options['timezone']);
  462. $ret = $date->format($format);
  463. if (!empty($options['oclock'])) {
  464. switch ($format) {
  465. case FORMAT_NICE_YMDHM:
  466. case FORMAT_NICE_YMDHMS:
  467. case FORMAT_NICE_HM:
  468. case FORMAT_NICE_HMS:
  469. $ret .= ' ' . __d('tools', 'o\'clock');
  470. break;
  471. }
  472. }
  473. return $ret;
  474. }
  475. /**
  476. * Takes time as hh:mm:ss or YYYY-MM-DD hh:mm:ss
  477. *
  478. * @param string $time
  479. * @return string Time in format hh:mm
  480. */
  481. public static function niceTime($time) {
  482. if (($pos = strpos($time, ' ')) !== false) {
  483. $time = substr($time, $pos + 1);
  484. }
  485. return substr($time, 0, 5);
  486. }
  487. /**
  488. * @return string
  489. */
  490. protected static function _getDefaultOutputTimezone() {
  491. return Configure::read('App.defaultOutputTimezone') ?: date_default_timezone_get();
  492. }
  493. /**
  494. * Return the translation to a specific week day
  495. *
  496. * @param int $day
  497. * 0=sunday to 7=saturday (default numbers)
  498. * @param bool $abbr (if abbreviation should be returned)
  499. * @param int $offset int 0-6 (defaults to 0) [1 => 1=monday to 7=sunday]
  500. * @return string translatedText
  501. */
  502. public static function dayName($day, $abbr = false, $offset = 0) {
  503. $days = [
  504. 'long' => [
  505. 'Sunday',
  506. 'Monday',
  507. 'Tuesday',
  508. 'Wednesday',
  509. 'Thursday',
  510. 'Friday',
  511. 'Saturday',
  512. ],
  513. 'short' => [
  514. 'Sun',
  515. 'Mon',
  516. 'Tue',
  517. 'Wed',
  518. 'Thu',
  519. 'Fri',
  520. 'Sat',
  521. ],
  522. ];
  523. $day = (int)$day;
  524. if ($offset) {
  525. $day = ($day + $offset) % 7;
  526. }
  527. if ($abbr) {
  528. return __d('tools', $days['short'][$day]);
  529. }
  530. return __d('tools', $days['long'][$day]);
  531. }
  532. /**
  533. * Return the translation to a specific week day
  534. *
  535. * @param int $month
  536. * 1..12
  537. * @param bool $abbr (if abbreviation should be returned)
  538. * @param array $options
  539. * - appendDot (only for 3 letter abbr; defaults to false)
  540. * @return string translatedText
  541. */
  542. public static function monthName($month, $abbr = false, array $options = []) {
  543. $months = [
  544. 'long' => [
  545. 'January',
  546. 'February',
  547. 'March',
  548. 'April',
  549. 'May',
  550. 'June',
  551. 'July',
  552. 'August',
  553. 'September',
  554. 'October',
  555. 'November',
  556. 'December',
  557. ],
  558. 'short' => [
  559. 'Jan',
  560. 'Feb',
  561. 'Mar',
  562. 'Apr',
  563. 'May',
  564. 'Jun',
  565. 'Jul',
  566. 'Aug',
  567. 'Sep',
  568. 'Oct',
  569. 'Nov',
  570. 'Dec',
  571. ],
  572. ];
  573. $month = (int)($month - 1);
  574. if (!$abbr) {
  575. return __d('tools', $months['long'][$month]);
  576. }
  577. $monthName = __d('tools', $months['short'][$month]);
  578. if (!empty($options['appendDot']) && strlen(__d('tools', $months['long'][$month])) > 3) {
  579. $monthName .= '.';
  580. }
  581. return $monthName;
  582. }
  583. /**
  584. * Months
  585. *
  586. * @param int[] $monthKeys
  587. * @param array $options
  588. * @return string[]
  589. */
  590. public static function monthNames(array $monthKeys = [], array $options = []) {
  591. if (!$monthKeys) {
  592. $monthKeys = range(1, 12);
  593. }
  594. $res = [];
  595. $abbr = isset($options['abbr']) ? $options['abbr'] : false;
  596. foreach ($monthKeys as $key) {
  597. $res[static::pad($key)] = static::monthName($key, $abbr, $options);
  598. }
  599. return $res;
  600. }
  601. /**
  602. * Weekdays
  603. *
  604. * @param int[] $dayKeys
  605. * @param array $options
  606. * @return string[]
  607. */
  608. public static function dayNames(array $dayKeys = [], array $options = []) {
  609. if (!$dayKeys) {
  610. $dayKeys = range(0, 6);
  611. }
  612. $res = [];
  613. $abbr = isset($options['abbr']) ? $options['abbr'] : false;
  614. $offset = isset($options['offset']) ? $options['offset'] : 0;
  615. foreach ($dayKeys as $key) {
  616. $res[$key] = static::dayName($key, $abbr, $offset);
  617. }
  618. return $res;
  619. }
  620. /**
  621. * Returns the difference between a time and now in a "fuzzy" way.
  622. * Note that unlike [span], the "local" timestamp will always be the
  623. * current time. Displaying a fuzzy time instead of a date is usually
  624. * faster to read and understand.
  625. *
  626. * $span = fuzzy(time() - 10); // "moments ago"
  627. * $span = fuzzy(time() + 20); // "in moments"
  628. *
  629. * @param int $timestamp "remote" timestamp
  630. * @return string
  631. */
  632. public static function fuzzy($timestamp) {
  633. // Determine the difference in seconds
  634. $offset = abs(time() - $timestamp);
  635. return static::fuzzyFromOffset($offset, $timestamp <= time());
  636. }
  637. /**
  638. * @param int $offset in seconds
  639. * @param bool|string|null $past (defaults to null: return plain text)
  640. * - new: if not boolean but a string use this as translating text
  641. * @return string text (i18n!)
  642. */
  643. public static function fuzzyFromOffset($offset, $past = null) {
  644. if ($offset <= MINUTE) {
  645. $span = 'moments';
  646. } elseif ($offset < (MINUTE * 20)) {
  647. $span = 'a few minutes';
  648. } elseif ($offset < HOUR) {
  649. $span = 'less than an hour';
  650. } elseif ($offset < (HOUR * 4)) {
  651. $span = 'a couple of hours';
  652. } elseif ($offset < DAY) {
  653. $span = 'less than a day';
  654. } elseif ($offset < (DAY * 2)) {
  655. $span = 'about a day';
  656. } elseif ($offset < (DAY * 4)) {
  657. $span = 'a couple of days';
  658. } elseif ($offset < WEEK) {
  659. $span = 'less than a week';
  660. } elseif ($offset < (WEEK * 2)) {
  661. $span = 'about a week';
  662. } elseif ($offset < MONTH) {
  663. $span = 'less than a month';
  664. } elseif ($offset < (MONTH * 2)) {
  665. $span = 'about a month';
  666. } elseif ($offset < (MONTH * 4)) {
  667. $span = 'a couple of months';
  668. } elseif ($offset < YEAR) {
  669. $span = 'less than a year';
  670. } elseif ($offset < (YEAR * 2)) {
  671. $span = 'about a year';
  672. } elseif ($offset < (YEAR * 4)) {
  673. $span = 'a couple of years';
  674. } elseif ($offset < (YEAR * 8)) {
  675. $span = 'a few years';
  676. } elseif ($offset < (YEAR * 12)) {
  677. $span = 'about a decade';
  678. } elseif ($offset < (YEAR * 24)) {
  679. $span = 'a couple of decades';
  680. } elseif ($offset < (YEAR * 64)) {
  681. $span = 'several decades';
  682. } else {
  683. $span = 'a long time';
  684. }
  685. if ($past === true) {
  686. // This is in the past
  687. return __d('tools', '%s ago', __d('tools', $span));
  688. }
  689. if ($past === false) {
  690. // This in the future
  691. return __d('tools', 'in %s', __d('tools', $span));
  692. }
  693. if ($past !== null) {
  694. // Custom translation
  695. return __d('tools', $past, __d('tools', $span));
  696. }
  697. return __d('tools', $span);
  698. }
  699. /**
  700. * Time length to human readable format.
  701. *
  702. * @param int $seconds
  703. * @param string|null $format
  704. * @param array $options
  705. * - boolean v: verbose
  706. * - boolean zero: if false: 0 days 5 hours => 5 hours etc.
  707. * - int: accuracy (how many sub-formats displayed?) //TODO
  708. * @return string
  709. * @see timeAgoInWords()
  710. */
  711. public static function lengthOfTime($seconds, $format = null, array $options = []) {
  712. $defaults = ['verbose' => true, 'zero' => false, 'separator' => ', ', 'default' => ''];
  713. $options += $defaults;
  714. if (!$options['verbose']) {
  715. $s = [
  716. 'm' => 'mth',
  717. 'd' => 'd',
  718. 'h' => 'h',
  719. 'i' => 'm',
  720. 's' => 's',
  721. ];
  722. $p = $s;
  723. } else {
  724. $s = [
  725. 'm' => ' ' . __d('tools', 'Month'), # translated
  726. 'd' => ' ' . __d('tools', 'Day'),
  727. 'h' => ' ' . __d('tools', 'Hour'),
  728. 'i' => ' ' . __d('tools', 'Minute'),
  729. 's' => ' ' . __d('tools', 'Second'),
  730. ];
  731. $p = [
  732. 'm' => ' ' . __d('tools', 'Months'), # translated
  733. 'd' => ' ' . __d('tools', 'Days'),
  734. 'h' => ' ' . __d('tools', 'Hours'),
  735. 'i' => ' ' . __d('tools', 'Minutes'),
  736. 's' => ' ' . __d('tools', 'Seconds'),
  737. ];
  738. }
  739. if (!isset($format)) {
  740. if (floor($seconds / DAY) > 0) {
  741. $format = 'Dh';
  742. } elseif (floor($seconds / 3600) > 0) {
  743. $format = 'Hi';
  744. } elseif (floor($seconds / 60) > 0) {
  745. $format = 'Is';
  746. } else {
  747. $format = 'S';
  748. }
  749. }
  750. $ret = '';
  751. $j = 0;
  752. $length = mb_strlen($format);
  753. for ($i = 0; $i < $length; $i++) {
  754. switch (mb_substr($format, $i, 1)) {
  755. case 'D':
  756. $str = floor($seconds / 86400);
  757. break;
  758. case 'd':
  759. $str = floor($seconds / 86400 % 30);
  760. break;
  761. case 'H':
  762. $str = floor($seconds / 3600);
  763. break;
  764. case 'h':
  765. $str = floor($seconds / 3600 % 24);
  766. break;
  767. case 'I':
  768. $str = floor($seconds / 60);
  769. break;
  770. case 'i':
  771. $str = floor($seconds / 60 % 60);
  772. break;
  773. case 'S':
  774. $str = $seconds;
  775. break;
  776. case 's':
  777. $str = floor($seconds % 60);
  778. break;
  779. default:
  780. return '';
  781. }
  782. if ($str > 0 || $j > 0 || $options['zero'] || $i === mb_strlen($format) - 1) {
  783. if ($j > 0) {
  784. $ret .= $options['separator'];
  785. }
  786. $j++;
  787. $x = mb_strtolower(mb_substr($format, $i, 1));
  788. if ($str === 1) {
  789. $ret .= $str . $s[$x];
  790. } else {
  791. $title = $p[$x];
  792. if (!empty($options['plural'])) {
  793. if (mb_substr($title, -1, 1) === 'e') {
  794. $title .= $options['plural'];
  795. }
  796. }
  797. $ret .= $str . $title;
  798. }
  799. }
  800. }
  801. return $ret;
  802. }
  803. /**
  804. * Time relative to NOW in human readable format - absolute (negative as well as positive)
  805. * //TODO: make "now" adjustable
  806. *
  807. * @param mixed $date
  808. * @param string|null $format Format
  809. * @param array $options Options
  810. * - default, separator
  811. * - boolean zero: if false: 0 days 5 hours => 5 hours etc.
  812. * - verbose/past/future: string with %s or boolean true/false
  813. * @return string|array
  814. */
  815. public static function relLengthOfTime($date, $format = null, array $options = []) {
  816. $dateTime = $date;
  817. if ($date !== null && !is_object($date)) {
  818. $dateTime = static::parse($date);
  819. }
  820. if ($dateTime !== null) {
  821. $date = $dateTime->format('U');
  822. $sec = time() - $date;
  823. $type = ($sec > 0) ? -1 : (($sec < 0) ? 1 : 0);
  824. $sec = abs($sec);
  825. } else {
  826. $sec = 0;
  827. $type = 0;
  828. }
  829. $defaults = [
  830. 'verbose' => __d('tools', 'justNow'), 'zero' => false, 'separator' => ', ',
  831. 'future' => __d('tools', 'In %s'), 'past' => __d('tools', '%s ago'), 'default' => ''];
  832. $options += $defaults;
  833. $ret = static::lengthOfTime($sec, $format, $options);
  834. if ($type == 1) {
  835. if ($options['future'] !== false) {
  836. return sprintf($options['future'], $ret);
  837. }
  838. return ['future' => $ret];
  839. }
  840. if ($type == -1) {
  841. if ($options['past'] !== false) {
  842. return sprintf($options['past'], $ret);
  843. }
  844. return ['past' => $ret];
  845. }
  846. if ($options['verbose'] !== false) {
  847. return $options['verbose'];
  848. }
  849. return $options['default'];
  850. }
  851. /**
  852. * Convenience method to convert a given date
  853. *
  854. * @deprecated
  855. *
  856. * @param string $oldDateString
  857. * @param string $newDateFormatString
  858. * @param int|null $timezone User's timezone
  859. * @return string Formatted date
  860. */
  861. public static function convertDate($oldDateString, $newDateFormatString, $timezone = null) {
  862. $Date = new CakeTime($oldDateString, $timezone);
  863. return $Date->format($newDateFormatString);
  864. }
  865. /**
  866. * Returns true if given datetime string was day before yesterday.
  867. *
  868. * @param \Cake\Chronos\ChronosInterface $date Datetime
  869. * @return bool True if datetime string was day before yesterday
  870. */
  871. public static function wasDayBeforeYesterday($date) {
  872. return $date->toDateString() === static::now()->subDays(2)->toDateString();
  873. }
  874. /**
  875. * Returns true if given datetime string is the day after tomorrow.
  876. *
  877. * @param \Cake\Chronos\ChronosInterface $date Datetime
  878. * @return bool True if datetime string is day after tomorrow
  879. */
  880. public static function isDayAfterTomorrow($date) {
  881. return $date->toDateString() === static::now()->addDays(2)->toDateString();
  882. }
  883. /**
  884. * Returns true if given datetime string is not today AND is in the future.
  885. *
  886. * @param string $dateString Datetime string or Unix timestamp
  887. * @param int|null $timezone User's timezone
  888. * @return bool True if datetime is not today AND is in the future
  889. */
  890. public static function isNotTodayAndInTheFuture($dateString, $timezone = null) {
  891. $date = new CakeTime($dateString, $timezone);
  892. $date = $date->format('U');
  893. return date(FORMAT_DB_DATE, $date) > date(FORMAT_DB_DATE, time());
  894. }
  895. /**
  896. * Returns true if given datetime string is not now AND is in the future.
  897. *
  898. * @param string $dateString Datetime string or Unix timestamp
  899. * @param int|null $timezone User's timezone
  900. * @return bool True if datetime is not today AND is in the future
  901. */
  902. public static function isInTheFuture($dateString, $timezone = null) {
  903. $date = new CakeTime($dateString, $timezone);
  904. $date = $date->format('U');
  905. return date(FORMAT_DB_DATETIME, $date) > date(FORMAT_DB_DATETIME, time());
  906. }
  907. /**
  908. * Try to parse date from various input formats
  909. * - DD.MM.YYYY, DD/MM/YYYY, YYYY-MM-DD, YYYY, YYYY-MM, ...
  910. * - i18n: Today, Yesterday, Tomorrow
  911. *
  912. * @param string $date to parse
  913. * @param string|null $format Format to parse (null = auto)
  914. * @param string $type
  915. * - start: first second of this interval
  916. * - end: last second of this interval
  917. * @return string timestamp
  918. */
  919. public static function parseLocalizedDate($date, $format = null, $type = 'start') {
  920. $date = trim($date);
  921. $i18n = [
  922. strtolower(__d('tools', 'Today')) => ['start' => date(FORMAT_DB_DATETIME, mktime(0, 0, 0, date('m'), date('d'), date('Y'))), 'end' => date(FORMAT_DB_DATETIME, mktime(23, 59, 59, date('m'), date('d'), date('Y')))],
  923. strtolower(__d('tools', 'Tomorrow')) => ['start' => date(FORMAT_DB_DATETIME, mktime(0, 0, 0, date('m'), date('d'), date('Y')) + DAY), 'end' => date(FORMAT_DB_DATETIME, mktime(23, 59, 59, date('m'), date('d'), date('Y')) + DAY)],
  924. strtolower(__d('tools', 'Yesterday')) => ['start' => date(FORMAT_DB_DATETIME, mktime(0, 0, 0, date('m'), date('d'), date('Y')) - DAY), 'end' => date(FORMAT_DB_DATETIME, mktime(23, 59, 59, date('m'), date('d'), date('Y')) - DAY)],
  925. strtolower(__d('tools', 'The day after tomorrow')) => ['start' => date(FORMAT_DB_DATETIME, mktime(0, 0, 0, date('m'), date('d'), date('Y')) + 2 * DAY), 'end' => date(FORMAT_DB_DATETIME, mktime(23, 59, 59, date('m'), date('d'), date('Y')) + 2 * DAY)],
  926. strtolower(__d('tools', 'The day before yesterday')) => ['start' => date(FORMAT_DB_DATETIME, mktime(0, 0, 0, date('m'), date('d'), date('Y')) - 2 * DAY), 'end' => date(FORMAT_DB_DATETIME, mktime(23, 59, 59, date('m'), date('d'), date('Y')) - 2 * DAY)],
  927. ];
  928. if (isset($i18n[strtolower($date)])) {
  929. return $i18n[strtolower($date)][$type];
  930. }
  931. if ($format) {
  932. $res = DateTime::createFromFormat($format, $date);
  933. $res = $res->format(FORMAT_DB_DATE) . ' ' . ($type === 'end' ? '23:59:59' : '00:00:00');
  934. return $res;
  935. }
  936. if (strpos($date, '.') !== false) {
  937. $explode = explode('.', $date, 3);
  938. $explode = array_reverse($explode);
  939. } elseif (strpos($date, '/') !== false) {
  940. $explode = explode('/', $date, 3);
  941. $explode = array_reverse($explode);
  942. } elseif (strpos($date, '-') !== false) {
  943. $explode = explode('-', $date, 3);
  944. } else {
  945. $explode = [$date];
  946. }
  947. if ($explode) {
  948. $count = count($explode);
  949. for ($i = 0; $i < $count; $i++) {
  950. $explode[$i] = static::pad($explode[$i]);
  951. }
  952. $explode[0] = static::pad($explode[0], 4, '20');
  953. if (count($explode) === 3) {
  954. return implode('-', $explode) . ' ' . ($type === 'end' ? '23:59:59' : '00:00:00');
  955. }
  956. if (count($explode) === 2) {
  957. return implode('-', $explode) . '-' . ($type === 'end' ? static::daysInMonth($explode[0], $explode[1]) : '01') . ' ' . ($type === 'end' ? '23:59:59' : '00:00:00');
  958. }
  959. return $explode[0] . '-' . ($type === 'end' ? '12' : '01') . '-' . ($type === 'end' ? '31' : '01') . ' ' . ($type === 'end' ? '23:59:59' : '00:00:00');
  960. }
  961. return '';
  962. }
  963. /**
  964. * Parse a period (from ... to)
  965. *
  966. * @param string $searchString Search string to parse
  967. * @param array $options
  968. * - separator (defaults to space [ ])
  969. * - format (defaults to Y-m-d H:i:s)
  970. * @return array period [0=>min, 1=>max]
  971. */
  972. public static function period($searchString, array $options = []) {
  973. if (strpos($searchString, ' ') !== false) {
  974. $filters = explode(' ', $searchString);
  975. $filters = [array_shift($filters), array_pop($filters)];
  976. } else {
  977. $filters = [$searchString, $searchString];
  978. }
  979. $min = $filters[0];
  980. $max = $filters[1];
  981. $min = static::parseLocalizedDate($min);
  982. $max = static::parseLocalizedDate($max, null, 'end');
  983. return [$min, $max];
  984. }
  985. /**
  986. * Return SQL snippet for a period (beginning till end).
  987. *
  988. * @param string $searchString to parse
  989. * @param string $fieldName (Model.field)
  990. * @param array $options (see Time::period)
  991. * @return string query SQL Query
  992. */
  993. public static function periodAsSql($searchString, $fieldName, array $options = []) {
  994. $period = static::period($searchString, $options);
  995. return static::daysAsSql($period[0], $period[1], $fieldName);
  996. }
  997. /**
  998. * Returns a partial SQL string to search for all records between two dates.
  999. *
  1000. * @param int|string|\DateTime $begin UNIX timestamp, strtotime() valid string or DateTime object
  1001. * @param int|string|\DateTime $end UNIX timestamp, strtotime() valid string or DateTime object
  1002. * @param string $fieldName Name of database field to compare with
  1003. * @param string|\DateTimeZone|null $timezone Timezone string or DateTimeZone object
  1004. * @return string Partial SQL string.
  1005. */
  1006. public static function daysAsSql($begin, $end, $fieldName, $timezone = null) {
  1007. $begin = new CakeTime($begin, $timezone);
  1008. $begin = $begin->format('U');
  1009. $end = new CakeTime($end, $timezone);
  1010. $end = $end->format('U');
  1011. $begin = date('Y-m-d', $begin) . ' 00:00:00';
  1012. $end = date('Y-m-d', $end) . ' 23:59:59';
  1013. return "($fieldName >= '$begin') AND ($fieldName <= '$end')";
  1014. }
  1015. /**
  1016. * Returns a partial SQL string to search for all records between two times
  1017. * occurring on the same day.
  1018. *
  1019. * @param int|string|\DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
  1020. * @param string $fieldName Name of database field to compare with
  1021. * @param string|\DateTimeZone|null $timezone Timezone string or DateTimeZone object
  1022. * @return string Partial SQL string.
  1023. */
  1024. public static function dayAsSql($dateString, $fieldName, $timezone = null) {
  1025. return static::daysAsSql($dateString, $dateString, $fieldName, $timezone);
  1026. }
  1027. /**
  1028. * Hours, minutes
  1029. * e.g. 9.3 => 9.5
  1030. *
  1031. * @param int $value
  1032. * @return float
  1033. */
  1034. public static function standardToDecimalTime($value) {
  1035. $base = (int)$value;
  1036. $tmp = $value - $base;
  1037. $tmp *= 100;
  1038. $tmp *= 1 / 60;
  1039. $value = $base + $tmp;
  1040. return $value;
  1041. }
  1042. /**
  1043. * Hours, minutes
  1044. * e.g. 9.5 => 9.3
  1045. * with pad=2: 9.30
  1046. *
  1047. * @param int $value
  1048. * @param string|null $pad
  1049. * @param string $decPoint
  1050. * @return string
  1051. */
  1052. public static function decimalToStandardTime($value, $pad = null, $decPoint = '.') {
  1053. $base = (int)$value;
  1054. $tmp = $value - $base;
  1055. $tmp /= 1 / 60;
  1056. $tmp /= 100;
  1057. $value = $base + $tmp;
  1058. if ($pad === null) {
  1059. return (string)$value;
  1060. }
  1061. return number_format($value, $pad, $decPoint, '');
  1062. }
  1063. /**
  1064. * Parse 2,5 - 2.5 2:30 2:31:58 or even 2011-11-12 10:10:10
  1065. * now supports negative values like -2,5 -2,5 -2:30 -:30 or -4
  1066. *
  1067. * @param string $duration
  1068. * @param string[] $allowed
  1069. * @return int Seconds
  1070. */
  1071. public static function parseLocalTime($duration, array $allowed = [':', '.', ',']) {
  1072. if (empty($duration)) {
  1073. return 0;
  1074. }
  1075. $parts = explode(' ', $duration);
  1076. $duration = array_pop($parts);
  1077. if (strpos($duration, '.') !== false && in_array('.', $allowed)) {
  1078. $duration = static::decimalToStandardTime($duration, 2, ':');
  1079. } elseif (strpos($duration, ',') !== false && in_array(',', $allowed)) {
  1080. $duration = str_replace(',', '.', $duration);
  1081. $duration = static::decimalToStandardTime($duration, 2, ':');
  1082. }
  1083. // now there is only the time schema left...
  1084. $pieces = explode(':', $duration, 3);
  1085. $res = 0;
  1086. $hours = abs((int)$pieces[0]) * HOUR;
  1087. //echo pre($hours);
  1088. $isNegative = (strpos((string)$pieces[0], '-') !== false ? true : false);
  1089. if (count($pieces) === 3) {
  1090. $res += $hours + ((int)$pieces[1]) * MINUTE + ((int)$pieces[2]) * SECOND;
  1091. } elseif (count($pieces) === 2) {
  1092. $res += $hours + ((int)$pieces[1]) * MINUTE;
  1093. } else {
  1094. $res += $hours;
  1095. }
  1096. if ($isNegative) {
  1097. return -$res;
  1098. }
  1099. return $res;
  1100. }
  1101. /**
  1102. * Parse 2022-11-12 or 12.11.2022 or even 12.11.22
  1103. *
  1104. * @param string $date
  1105. * @param string[] $allowed
  1106. * @return int Seconds
  1107. */
  1108. public static function parseLocalDate($date, array $allowed = ['.', '-']) {
  1109. $datePieces = explode(' ', $date, 2);
  1110. $date = array_shift($datePieces);
  1111. if (strpos($date, '.') !== false) {
  1112. $pieces = explode('.', $date);
  1113. $year = $pieces[2];
  1114. if (strlen($year) === 2) {
  1115. if ($year < 50) {
  1116. $year = '20' . $year;
  1117. } else {
  1118. $year = '19' . $year;
  1119. }
  1120. }
  1121. $date = mktime(0, 0, 0, $pieces[1], $pieces[0], $year);
  1122. } elseif (strpos($date, '-') !== false) {
  1123. //$pieces = explode('-', $date);
  1124. $date = strtotime($date);
  1125. } else {
  1126. return 0;
  1127. }
  1128. return $date;
  1129. }
  1130. /**
  1131. * Returns nicely formatted duration difference
  1132. * as string like 2:30 (H:MM) or 2:30:06 (H:MM:SS) etc.
  1133. * Note that the more than days is currently not supported accurately.
  1134. *
  1135. * E.g. for days and hours set format to: $d:$H
  1136. *
  1137. * @param int|\DateInterval $duration Duration in seconds or as DateInterval object
  1138. * @param string $format Defaults to hours, minutes and seconds
  1139. * @return string Time
  1140. */
  1141. public static function duration($duration, $format = '%h:%I:%S') {
  1142. if (!$duration instanceof \DateInterval) {
  1143. $d1 = new CakeTime();
  1144. $d2 = new CakeTime();
  1145. $d2->add(new DateInterval('PT' . $duration . 'S'));
  1146. $duration = $d2->diff($d1);
  1147. }
  1148. if (stripos($format, 'd') === false && $duration->d) {
  1149. $duration->h += $duration->d * 24;
  1150. }
  1151. if (stripos($format, 'h') === false && $duration->h) {
  1152. $duration->i += $duration->h * 60;
  1153. }
  1154. if (stripos($format, 'i') === false && $duration->i) {
  1155. $duration->s += $duration->m * 60;
  1156. }
  1157. return $duration->format($format);
  1158. }
  1159. /**
  1160. * Returns nicely formatted duration difference
  1161. * as string like 2:30 or 2:30:06.
  1162. * Note that the more than hours is currently not supported.
  1163. *
  1164. * Note that duration with DateInterval supports only values < month with accuracy,
  1165. * as it approximates month as "30".
  1166. *
  1167. * @param int|\DateInterval $duration Duration in seconds or as DateInterval object
  1168. * @param string $format Defaults to hours and minutes
  1169. * @return string Time
  1170. * @deprecated Use duration() instead?
  1171. */
  1172. public static function buildTime($duration, $format = 'H:MM:SS') {
  1173. if ($duration instanceof \DateInterval) {
  1174. $m = $duration->invert ? -1 : 1;
  1175. $duration = ($duration->y * YEAR) +
  1176. ($duration->m * MONTH) +
  1177. ($duration->d * DAY) +
  1178. ($duration->h * HOUR) +
  1179. ($duration->i * MINUTE) +
  1180. $duration->s;
  1181. $duration *= $m;
  1182. }
  1183. if ($duration < 0) {
  1184. $duration = abs($duration);
  1185. $isNegative = true;
  1186. }
  1187. $minutes = $duration % HOUR;
  1188. $hours = ($duration - $minutes) / HOUR;
  1189. $res = [];
  1190. if (strpos($format, 'H') !== false) {
  1191. $res[] = (int)$hours . ':' . static::pad((int)($minutes / MINUTE));
  1192. } else {
  1193. $res[] = (int)($minutes / MINUTE);
  1194. }
  1195. if (strpos($format, 'SS') !== false) {
  1196. $seconds = $duration % MINUTE;
  1197. $res[] = static::pad((int)$seconds);
  1198. }
  1199. $res = implode(':', $res);
  1200. if (!empty($isNegative)) {
  1201. $res = '-' . $res;
  1202. }
  1203. return $res;
  1204. }
  1205. /**
  1206. * Return strings like 2:33:99 from seconds etc
  1207. *
  1208. * @param int $duration Duration in seconds
  1209. * @return string Time
  1210. */
  1211. public static function buildDefaultTime($duration) {
  1212. $minutes = $duration % HOUR;
  1213. $duration = $duration - $minutes;
  1214. $hours = $duration / HOUR;
  1215. $seconds = $minutes % MINUTE;
  1216. return static::pad($hours) . ':' . static::pad($minutes / MINUTE) . ':' . static::pad($seconds / SECOND);
  1217. }
  1218. /**
  1219. * @param string $value
  1220. * @param int $length
  1221. * @param string $string
  1222. * @return string
  1223. */
  1224. public static function pad($value, $length = 2, $string = '0') {
  1225. return str_pad((int)$value, $length, $string, STR_PAD_LEFT);
  1226. }
  1227. }