Date.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <?php
  2. /**
  3. * Date helper.
  4. *
  5. * @package KO7
  6. * @category Helpers
  7. *
  8. * @copyright (c) 2007-2016 Kohana Team
  9. * @copyright (c) since 2016 Koseven Team
  10. * @license https://koseven.dev/LICENSE
  11. */
  12. class KO7_Date {
  13. // Second amounts for various time increments
  14. const YEAR = 31556926;
  15. const MONTH = 2629744;
  16. const WEEK = 604800;
  17. const DAY = 86400;
  18. const HOUR = 3600;
  19. const MINUTE = 60;
  20. // Available formats for Date::months()
  21. const MONTHS_LONG = '%B';
  22. const MONTHS_SHORT = '%b';
  23. /**
  24. * Default timestamp format for formatted_time
  25. * @var string
  26. */
  27. public static $timestamp_format = 'Y-m-d H:i:s';
  28. /**
  29. * Timezone for formatted_time
  30. * @link http://uk2.php.net/manual/en/timezones.php
  31. * @var string
  32. */
  33. public static $timezone;
  34. /**
  35. * Returns the offset (in seconds) between two time zones. Use this to
  36. * display dates to users in different time zones.
  37. *
  38. * $seconds = Date::offset('America/Chicago', 'GMT');
  39. *
  40. * [!!] A list of time zones that PHP supports can be found at
  41. * <http://php.net/timezones>.
  42. *
  43. * @param string $remote timezone that to find the offset of
  44. * @param string $local timezone used as the baseline
  45. * @param mixed $now UNIX timestamp or date string
  46. * @return integer
  47. */
  48. public static function offset($remote, $local = NULL, $now = NULL)
  49. {
  50. if ($local === NULL)
  51. {
  52. // Use the default timezone
  53. $local = date_default_timezone_get();
  54. }
  55. if (is_int($now))
  56. {
  57. // Convert the timestamp into a string
  58. $now = date(DateTime::RFC2822, $now);
  59. }
  60. // Create timezone objects
  61. $zone_remote = new DateTimeZone($remote);
  62. $zone_local = new DateTimeZone($local);
  63. // Create date objects from timezones
  64. $time_remote = new DateTime($now, $zone_remote);
  65. $time_local = new DateTime($now, $zone_local);
  66. // Find the offset
  67. $offset = $zone_remote->getOffset($time_remote) - $zone_local->getOffset($time_local);
  68. return $offset;
  69. }
  70. /**
  71. * Number of seconds in a minute, incrementing by a step. Typically used as
  72. * a shortcut for generating a list that can used in a form.
  73. *
  74. * $seconds = Date::seconds(); // 01, 02, 03, ..., 58, 59, 60
  75. *
  76. * @param integer $step amount to increment each step by, 1 to 30
  77. * @param integer $start start value
  78. * @param integer $end end value
  79. * @return array A mirrored (foo => foo) array from 1-60.
  80. */
  81. public static function seconds($step = 1, $start = 0, $end = 60)
  82. {
  83. // Always integer
  84. $step = (int) $step;
  85. $seconds = [];
  86. for ($i = $start; $i < $end; $i += $step)
  87. {
  88. $seconds[$i] = sprintf('%02d', $i);
  89. }
  90. return $seconds;
  91. }
  92. /**
  93. * Number of minutes in an hour, incrementing by a step. Typically used as
  94. * a shortcut for generating a list that can be used in a form.
  95. *
  96. * $minutes = Date::minutes(); // 05, 10, 15, ..., 50, 55, 60
  97. *
  98. * @uses Date::seconds
  99. * @param integer $step amount to increment each step by, 1 to 30
  100. * @return array A mirrored (foo => foo) array from 1-60.
  101. */
  102. public static function minutes($step = 5)
  103. {
  104. // Because there are the same number of minutes as seconds in this set,
  105. // we choose to re-use seconds(), rather than creating an entirely new
  106. // function. Shhhh, it's cheating! ;) There are several more of these
  107. // in the following methods.
  108. return Date::seconds($step);
  109. }
  110. /**
  111. * Number of hours in a day. Typically used as a shortcut for generating a
  112. * list that can be used in a form.
  113. *
  114. * $hours = Date::hours(); // 01, 02, 03, ..., 10, 11, 12
  115. *
  116. * @param integer $step amount to increment each step by
  117. * @param boolean $long use 24-hour time
  118. * @param integer $start the hour to start at
  119. * @return array A mirrored (foo => foo) array from start-12 or start-23.
  120. */
  121. public static function hours($step = 1, $long = FALSE, $start = NULL)
  122. {
  123. // Default values
  124. $step = (int) $step;
  125. $long = (bool) $long;
  126. $hours = [];
  127. // Set the default start if none was specified.
  128. if ($start === NULL)
  129. {
  130. $start = ($long === FALSE) ? 1 : 0;
  131. }
  132. $hours = [];
  133. // 24-hour time has 24 hours, instead of 12
  134. $size = ($long === TRUE) ? 23 : 12;
  135. for ($i = $start; $i <= $size; $i += $step)
  136. {
  137. $hours[$i] = (string) $i;
  138. }
  139. return $hours;
  140. }
  141. /**
  142. * Returns AM or PM, based on a given hour (in 24 hour format).
  143. *
  144. * $type = Date::ampm(12); // PM
  145. * $type = Date::ampm(1); // AM
  146. *
  147. * @param integer $hour number of the hour
  148. * @return string
  149. */
  150. public static function ampm($hour)
  151. {
  152. // Always integer
  153. $hour = (int) $hour;
  154. return ($hour > 11) ? 'PM' : 'AM';
  155. }
  156. /**
  157. * Adjusts a non-24-hour number into a 24-hour number.
  158. *
  159. * $hour = Date::adjust(3, 'pm'); // 15
  160. *
  161. * @param integer $hour hour to adjust
  162. * @param string $ampm AM or PM
  163. * @return string
  164. */
  165. public static function adjust($hour, $ampm)
  166. {
  167. $hour = (int) $hour;
  168. $ampm = strtolower($ampm);
  169. switch ($ampm)
  170. {
  171. case 'am':
  172. if ($hour == 12)
  173. {
  174. $hour = 0;
  175. }
  176. break;
  177. case 'pm':
  178. if ($hour < 12)
  179. {
  180. $hour += 12;
  181. }
  182. break;
  183. }
  184. return sprintf('%02d', $hour);
  185. }
  186. /**
  187. * Number of days in a given month and year. Typically used as a shortcut
  188. * for generating a list that can be used in a form.
  189. *
  190. * Date::days(4, 2010); // 1, 2, 3, ..., 28, 29, 30
  191. *
  192. * @param integer $month number of month
  193. * @param integer $year number of year to check month, defaults to the current year
  194. * @return array A mirrored (foo => foo) array of the days.
  195. */
  196. public static function days($month, $year = FALSE)
  197. {
  198. static $months;
  199. if ($year === FALSE)
  200. {
  201. // Use the current year by default
  202. $year = date('Y');
  203. }
  204. // Always integers
  205. $month = (int) $month;
  206. $year = (int) $year;
  207. // We use caching for months, because time functions are used
  208. if (empty($months[$year][$month]))
  209. {
  210. $months[$year][$month] = [];
  211. // Use date to find the number of days in the given month
  212. $total = date('t', mktime(1, 0, 0, $month, 1, $year)) + 1;
  213. for ($i = 1; $i < $total; $i++)
  214. {
  215. $months[$year][$month][$i] = (string) $i;
  216. }
  217. }
  218. return $months[$year][$month];
  219. }
  220. /**
  221. * Number of months in a year. Typically used as a shortcut for generating
  222. * a list that can be used in a form.
  223. *
  224. * By default a mirrored array of $month_number => $month_number is returned
  225. *
  226. * Date::months();
  227. * // aray(1 => 1, 2 => 2, 3 => 3, ..., 12 => 12)
  228. *
  229. * But you can customise this by passing in either Date::MONTHS_LONG
  230. *
  231. * Date::months(Date::MONTHS_LONG);
  232. * // array(1 => 'January', 2 => 'February', ..., 12 => 'December')
  233. *
  234. * Or Date::MONTHS_SHORT
  235. *
  236. * Date::months(Date::MONTHS_SHORT);
  237. * // array(1 => 'Jan', 2 => 'Feb', ..., 12 => 'Dec')
  238. *
  239. * @uses Date::hours
  240. * @param string $format The format to use for months
  241. * @return array An array of months based on the specified format
  242. */
  243. public static function months($format = NULL)
  244. {
  245. $months = [];
  246. if ($format === Date::MONTHS_LONG OR $format === Date::MONTHS_SHORT)
  247. {
  248. for ($i = 1; $i <= 12; ++$i)
  249. {
  250. $months[$i] = strftime($format, mktime(0, 0, 0, $i, 1));
  251. }
  252. }
  253. else
  254. {
  255. $months = Date::hours();
  256. }
  257. return $months;
  258. }
  259. /**
  260. * Returns an array of years between a starting and ending year. By default,
  261. * the the current year - 5 and current year + 5 will be used. Typically used
  262. * as a shortcut for generating a list that can be used in a form.
  263. *
  264. * $years = Date::years(2000, 2010); // 2000, 2001, ..., 2009, 2010
  265. *
  266. * @param integer $start starting year (default is current year - 5)
  267. * @param integer $end ending year (default is current year + 5)
  268. * @return array
  269. */
  270. public static function years($start = FALSE, $end = FALSE)
  271. {
  272. // Default values
  273. $start = ($start === FALSE) ? (date('Y') - 5) : (int) $start;
  274. $end = ($end === FALSE) ? (date('Y') + 5) : (int) $end;
  275. $years = [];
  276. for ($i = $start; $i <= $end; $i++)
  277. {
  278. $years[$i] = (string) $i;
  279. }
  280. return $years;
  281. }
  282. /**
  283. * Returns time difference between two timestamps, in human readable format.
  284. * If the second timestamp is not given, the current time will be used.
  285. * Also consider using [Date::fuzzy_span] when displaying a span.
  286. *
  287. * $span = Date::span(60, 182, 'minutes,seconds'); // array('minutes' => 2, 'seconds' => 2)
  288. * $span = Date::span(60, 182, 'minutes'); // 2
  289. *
  290. * @param integer $remote timestamp to find the span of
  291. * @param integer $local timestamp to use as the baseline
  292. * @param string $output formatting string
  293. * @return string when only a single output is requested
  294. * @return array associative list of all outputs requested
  295. */
  296. public static function span($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
  297. {
  298. // Normalize output
  299. $output = trim(strtolower( (string) $output));
  300. if ( ! $output)
  301. {
  302. // Invalid output
  303. return FALSE;
  304. }
  305. // Array with the output formats
  306. $output = preg_split('/[^a-z]+/', $output);
  307. // Convert the list of outputs to an associative array
  308. $output = array_combine($output, array_fill(0, count($output), 0));
  309. // Make the output values into keys
  310. extract(array_flip($output), EXTR_SKIP);
  311. if ($local === NULL)
  312. {
  313. // Calculate the span from the current time
  314. $local = time();
  315. }
  316. // Calculate timespan (seconds)
  317. $timespan = abs($remote - $local);
  318. if (isset($output['years']))
  319. {
  320. $timespan -= Date::YEAR * ($output['years'] = (int) floor($timespan / Date::YEAR));
  321. }
  322. if (isset($output['months']))
  323. {
  324. $timespan -= Date::MONTH * ($output['months'] = (int) floor($timespan / Date::MONTH));
  325. }
  326. if (isset($output['weeks']))
  327. {
  328. $timespan -= Date::WEEK * ($output['weeks'] = (int) floor($timespan / Date::WEEK));
  329. }
  330. if (isset($output['days']))
  331. {
  332. $timespan -= Date::DAY * ($output['days'] = (int) floor($timespan / Date::DAY));
  333. }
  334. if (isset($output['hours']))
  335. {
  336. $timespan -= Date::HOUR * ($output['hours'] = (int) floor($timespan / Date::HOUR));
  337. }
  338. if (isset($output['minutes']))
  339. {
  340. $timespan -= Date::MINUTE * ($output['minutes'] = (int) floor($timespan / Date::MINUTE));
  341. }
  342. // Seconds ago, 1
  343. if (isset($output['seconds']))
  344. {
  345. $output['seconds'] = $timespan;
  346. }
  347. if (count($output) === 1)
  348. {
  349. // Only a single output was requested, return it
  350. return array_pop($output);
  351. }
  352. // Return array
  353. return $output;
  354. }
  355. /**
  356. * Returns the difference between a time and now in a "fuzzy" way.
  357. * Displaying a fuzzy time instead of a date is usually faster to read and understand.
  358. *
  359. * $span = Date::fuzzy_span(time() - 10); // "moments ago"
  360. * $span = Date::fuzzy_span(time() + 20); // "in moments"
  361. *
  362. * A second parameter is available to manually set the "local" timestamp,
  363. * however this parameter shouldn't be needed in normal usage and is only
  364. * included for unit tests
  365. *
  366. * @param integer $timestamp "remote" timestamp
  367. * @param integer $local_timestamp "local" timestamp, defaults to time()
  368. * @return string
  369. */
  370. public static function fuzzy_span($timestamp, $local_timestamp = NULL)
  371. {
  372. $local_timestamp = ($local_timestamp === NULL) ? time() : (int) $local_timestamp;
  373. // Determine the difference in seconds
  374. $offset = abs($local_timestamp - $timestamp);
  375. if ($offset <= Date::MINUTE)
  376. {
  377. $span = 'moments';
  378. }
  379. elseif ($offset < (Date::MINUTE * 20))
  380. {
  381. $span = 'a few minutes';
  382. }
  383. elseif ($offset < Date::HOUR)
  384. {
  385. $span = 'less than an hour';
  386. }
  387. elseif ($offset < (Date::HOUR * 4))
  388. {
  389. $span = 'a couple of hours';
  390. }
  391. elseif ($offset < Date::DAY)
  392. {
  393. $span = 'less than a day';
  394. }
  395. elseif ($offset < (Date::DAY * 2))
  396. {
  397. $span = 'about a day';
  398. }
  399. elseif ($offset < (Date::DAY * 4))
  400. {
  401. $span = 'a couple of days';
  402. }
  403. elseif ($offset < Date::WEEK)
  404. {
  405. $span = 'less than a week';
  406. }
  407. elseif ($offset < (Date::WEEK * 2))
  408. {
  409. $span = 'about a week';
  410. }
  411. elseif ($offset < Date::MONTH)
  412. {
  413. $span = 'less than a month';
  414. }
  415. elseif ($offset < (Date::MONTH * 2))
  416. {
  417. $span = 'about a month';
  418. }
  419. elseif ($offset < (Date::MONTH * 4))
  420. {
  421. $span = 'a couple of months';
  422. }
  423. elseif ($offset < Date::YEAR)
  424. {
  425. $span = 'less than a year';
  426. }
  427. elseif ($offset < (Date::YEAR * 2))
  428. {
  429. $span = 'about a year';
  430. }
  431. elseif ($offset < (Date::YEAR * 4))
  432. {
  433. $span = 'a couple of years';
  434. }
  435. elseif ($offset < (Date::YEAR * 8))
  436. {
  437. $span = 'a few years';
  438. }
  439. elseif ($offset < (Date::YEAR * 12))
  440. {
  441. $span = 'about a decade';
  442. }
  443. elseif ($offset < (Date::YEAR * 24))
  444. {
  445. $span = 'a couple of decades';
  446. }
  447. elseif ($offset < (Date::YEAR * 64))
  448. {
  449. $span = 'several decades';
  450. }
  451. else
  452. {
  453. $span = 'a long time';
  454. }
  455. if ($timestamp <= $local_timestamp)
  456. {
  457. // This is in the past
  458. return $span.' ago';
  459. }
  460. else
  461. {
  462. // This in the future
  463. return 'in '.$span;
  464. }
  465. }
  466. /**
  467. * Converts a UNIX timestamp to DOS format. There are very few cases where
  468. * this is needed, but some binary formats use it (eg: zip files.)
  469. * Converting the other direction is done using {@link Date::dos2unix}.
  470. *
  471. * $dos = Date::unix2dos($unix);
  472. *
  473. * @param integer $timestamp UNIX timestamp
  474. * @return integer
  475. */
  476. public static function unix2dos($timestamp = FALSE)
  477. {
  478. $timestamp = ($timestamp === FALSE) ? getdate() : getdate($timestamp);
  479. if ($timestamp['year'] < 1980)
  480. {
  481. return (1 << 21 | 1 << 16);
  482. }
  483. $timestamp['year'] -= 1980;
  484. // What voodoo is this? I have no idea... Geert can explain it though,
  485. // and that's good enough for me.
  486. return ($timestamp['year'] << 25 | $timestamp['mon'] << 21 |
  487. $timestamp['mday'] << 16 | $timestamp['hours'] << 11 |
  488. $timestamp['minutes'] << 5 | $timestamp['seconds'] >> 1);
  489. }
  490. /**
  491. * Converts a DOS timestamp to UNIX format.There are very few cases where
  492. * this is needed, but some binary formats use it (eg: zip files.)
  493. * Converting the other direction is done using {@link Date::unix2dos}.
  494. *
  495. * $unix = Date::dos2unix($dos);
  496. *
  497. * @param integer $timestamp DOS timestamp
  498. * @return integer
  499. */
  500. public static function dos2unix($timestamp = FALSE)
  501. {
  502. $sec = 2 * ($timestamp & 0x1f);
  503. $min = ($timestamp >> 5) & 0x3f;
  504. $hrs = ($timestamp >> 11) & 0x1f;
  505. $day = ($timestamp >> 16) & 0x1f;
  506. $mon = ($timestamp >> 21) & 0x0f;
  507. $year = ($timestamp >> 25) & 0x7f;
  508. return mktime($hrs, $min, $sec, $mon, $day, $year + 1980);
  509. }
  510. /**
  511. * Returns a date/time string with the specified timestamp format
  512. *
  513. * $time = Date::formatted_time('5 minutes ago');
  514. *
  515. * @link http://www.php.net/manual/datetime.construct
  516. * @param string $datetime_str datetime string
  517. * @param string $timestamp_format timestamp format
  518. * @param string $timezone timezone identifier
  519. * @return string
  520. */
  521. public static function formatted_time($datetime_str = 'now', $timestamp_format = NULL, $timezone = NULL)
  522. {
  523. $timestamp_format = ($timestamp_format == NULL) ? Date::$timestamp_format : $timestamp_format;
  524. $timezone = ($timezone === NULL) ? Date::$timezone : $timezone;
  525. $tz = new DateTimeZone($timezone ? $timezone : date_default_timezone_get());
  526. $time = new DateTime($datetime_str, $tz);
  527. // Convert the time back to the expected timezone if required (in case the datetime_str provided a timezone,
  528. // offset or unix timestamp. This also ensures that the timezone reported by the object is correct on HHVM
  529. // (see https://github.com/facebook/hhvm/issues/2302).
  530. $time->setTimeZone($tz);
  531. return $time->format($timestamp_format);
  532. }
  533. }