You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

742 lines
17 KiB

7 years ago
  1. <?php
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP
  6. *
  7. * This content is released under the MIT License (MIT)
  8. *
  9. * Copyright (c) 2014 - 2017, British Columbia Institute of Technology
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to deal
  13. * in the Software without restriction, including without limitation the rights
  14. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. * copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in
  19. * all copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  27. * THE SOFTWARE.
  28. *
  29. * @package CodeIgniter
  30. * @author EllisLab Dev Team
  31. * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
  32. * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/)
  33. * @license http://opensource.org/licenses/MIT MIT License
  34. * @link https://codeigniter.com
  35. * @since Version 1.0.0
  36. * @filesource
  37. */
  38. defined('BASEPATH') OR exit('No direct script access allowed');
  39. /**
  40. * CodeIgniter Date Helpers
  41. *
  42. * @package CodeIgniter
  43. * @subpackage Helpers
  44. * @category Helpers
  45. * @author EllisLab Dev Team
  46. * @link https://codeigniter.com/user_guide/helpers/date_helper.html
  47. */
  48. // ------------------------------------------------------------------------
  49. if ( ! function_exists('now'))
  50. {
  51. /**
  52. * Get "now" time
  53. *
  54. * Returns time() based on the timezone parameter or on the
  55. * "time_reference" setting
  56. *
  57. * @param string
  58. * @return int
  59. */
  60. function now($timezone = NULL)
  61. {
  62. if (empty($timezone))
  63. {
  64. $timezone = config_item('time_reference');
  65. }
  66. if ($timezone === 'local' OR $timezone === date_default_timezone_get())
  67. {
  68. return time();
  69. }
  70. $datetime = new DateTime('now', new DateTimeZone($timezone));
  71. sscanf($datetime->format('j-n-Y G:i:s'), '%d-%d-%d %d:%d:%d', $day, $month, $year, $hour, $minute, $second);
  72. return mktime($hour, $minute, $second, $month, $day, $year);
  73. }
  74. }
  75. // ------------------------------------------------------------------------
  76. if ( ! function_exists('mdate'))
  77. {
  78. /**
  79. * Convert MySQL Style Datecodes
  80. *
  81. * This function is identical to PHPs date() function,
  82. * except that it allows date codes to be formatted using
  83. * the MySQL style, where each code letter is preceded
  84. * with a percent sign: %Y %m %d etc...
  85. *
  86. * The benefit of doing dates this way is that you don't
  87. * have to worry about escaping your text letters that
  88. * match the date codes.
  89. *
  90. * @param string
  91. * @param int
  92. * @return int
  93. */
  94. function mdate($datestr = '', $time = '')
  95. {
  96. if ($datestr === '')
  97. {
  98. return '';
  99. }
  100. elseif (empty($time))
  101. {
  102. $time = now();
  103. }
  104. $datestr = str_replace(
  105. '%\\',
  106. '',
  107. preg_replace('/([a-z]+?){1}/i', '\\\\\\1', $datestr)
  108. );
  109. return date($datestr, $time);
  110. }
  111. }
  112. // ------------------------------------------------------------------------
  113. if ( ! function_exists('standard_date'))
  114. {
  115. /**
  116. * Standard Date
  117. *
  118. * Returns a date formatted according to the submitted standard.
  119. *
  120. * As of PHP 5.2, the DateTime extension provides constants that
  121. * serve for the exact same purpose and are used with date().
  122. *
  123. * @todo Remove in version 3.1+.
  124. * @deprecated 3.0.0 Use PHP's native date() instead.
  125. * @link http://www.php.net/manual/en/class.datetime.php#datetime.constants.types
  126. *
  127. * @example date(DATE_RFC822, now()); // default
  128. * @example date(DATE_W3C, $time); // a different format and time
  129. *
  130. * @param string $fmt = 'DATE_RFC822' the chosen format
  131. * @param int $time = NULL Unix timestamp
  132. * @return string
  133. */
  134. function standard_date($fmt = 'DATE_RFC822', $time = NULL)
  135. {
  136. if (empty($time))
  137. {
  138. $time = now();
  139. }
  140. // Procedural style pre-defined constants from the DateTime extension
  141. if (strpos($fmt, 'DATE_') !== 0 OR defined($fmt) === FALSE)
  142. {
  143. return FALSE;
  144. }
  145. return date(constant($fmt), $time);
  146. }
  147. }
  148. // ------------------------------------------------------------------------
  149. if ( ! function_exists('timespan'))
  150. {
  151. /**
  152. * Timespan
  153. *
  154. * Returns a span of seconds in this format:
  155. * 10 days 14 hours 36 minutes 47 seconds
  156. *
  157. * @param int a number of seconds
  158. * @param int Unix timestamp
  159. * @param int a number of display units
  160. * @return string
  161. */
  162. function timespan($seconds = 1, $time = '', $units = 7)
  163. {
  164. $CI =& get_instance();
  165. $CI->lang->load('date');
  166. is_numeric($seconds) OR $seconds = 1;
  167. is_numeric($time) OR $time = time();
  168. is_numeric($units) OR $units = 7;
  169. $seconds = ($time <= $seconds) ? 1 : $time - $seconds;
  170. $str = array();
  171. $years = floor($seconds / 31557600);
  172. if ($years > 0)
  173. {
  174. $str[] = $years.' '.$CI->lang->line($years > 1 ? 'date_years' : 'date_year');
  175. }
  176. $seconds -= $years * 31557600;
  177. $months = floor($seconds / 2629743);
  178. if (count($str) < $units && ($years > 0 OR $months > 0))
  179. {
  180. if ($months > 0)
  181. {
  182. $str[] = $months.' '.$CI->lang->line($months > 1 ? 'date_months' : 'date_month');
  183. }
  184. $seconds -= $months * 2629743;
  185. }
  186. $weeks = floor($seconds / 604800);
  187. if (count($str) < $units && ($years > 0 OR $months > 0 OR $weeks > 0))
  188. {
  189. if ($weeks > 0)
  190. {
  191. $str[] = $weeks.' '.$CI->lang->line($weeks > 1 ? 'date_weeks' : 'date_week');
  192. }
  193. $seconds -= $weeks * 604800;
  194. }
  195. $days = floor($seconds / 86400);
  196. if (count($str) < $units && ($months > 0 OR $weeks > 0 OR $days > 0))
  197. {
  198. if ($days > 0)
  199. {
  200. $str[] = $days.' '.$CI->lang->line($days > 1 ? 'date_days' : 'date_day');
  201. }
  202. $seconds -= $days * 86400;
  203. }
  204. $hours = floor($seconds / 3600);
  205. if (count($str) < $units && ($days > 0 OR $hours > 0))
  206. {
  207. if ($hours > 0)
  208. {
  209. $str[] = $hours.' '.$CI->lang->line($hours > 1 ? 'date_hours' : 'date_hour');
  210. }
  211. $seconds -= $hours * 3600;
  212. }
  213. $minutes = floor($seconds / 60);
  214. if (count($str) < $units && ($days > 0 OR $hours > 0 OR $minutes > 0))
  215. {
  216. if ($minutes > 0)
  217. {
  218. $str[] = $minutes.' '.$CI->lang->line($minutes > 1 ? 'date_minutes' : 'date_minute');
  219. }
  220. $seconds -= $minutes * 60;
  221. }
  222. if (count($str) === 0)
  223. {
  224. $str[] = $seconds.' '.$CI->lang->line($seconds > 1 ? 'date_seconds' : 'date_second');
  225. }
  226. return implode(', ', $str);
  227. }
  228. }
  229. // ------------------------------------------------------------------------
  230. if ( ! function_exists('days_in_month'))
  231. {
  232. /**
  233. * Number of days in a month
  234. *
  235. * Takes a month/year as input and returns the number of days
  236. * for the given month/year. Takes leap years into consideration.
  237. *
  238. * @param int a numeric month
  239. * @param int a numeric year
  240. * @return int
  241. */
  242. function days_in_month($month = 0, $year = '')
  243. {
  244. if ($month < 1 OR $month > 12)
  245. {
  246. return 0;
  247. }
  248. elseif ( ! is_numeric($year) OR strlen($year) !== 4)
  249. {
  250. $year = date('Y');
  251. }
  252. if (defined('CAL_GREGORIAN'))
  253. {
  254. return cal_days_in_month(CAL_GREGORIAN, $month, $year);
  255. }
  256. if ($year >= 1970)
  257. {
  258. return (int) date('t', mktime(12, 0, 0, $month, 1, $year));
  259. }
  260. if ($month == 2)
  261. {
  262. if ($year % 400 === 0 OR ($year % 4 === 0 && $year % 100 !== 0))
  263. {
  264. return 29;
  265. }
  266. }
  267. $days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
  268. return $days_in_month[$month - 1];
  269. }
  270. }
  271. // ------------------------------------------------------------------------
  272. if ( ! function_exists('local_to_gmt'))
  273. {
  274. /**
  275. * Converts a local Unix timestamp to GMT
  276. *
  277. * @param int Unix timestamp
  278. * @return int
  279. */
  280. function local_to_gmt($time = '')
  281. {
  282. if ($time === '')
  283. {
  284. $time = time();
  285. }
  286. return mktime(
  287. gmdate('G', $time),
  288. gmdate('i', $time),
  289. gmdate('s', $time),
  290. gmdate('n', $time),
  291. gmdate('j', $time),
  292. gmdate('Y', $time)
  293. );
  294. }
  295. }
  296. // ------------------------------------------------------------------------
  297. if ( ! function_exists('gmt_to_local'))
  298. {
  299. /**
  300. * Converts GMT time to a localized value
  301. *
  302. * Takes a Unix timestamp (in GMT) as input, and returns
  303. * at the local value based on the timezone and DST setting
  304. * submitted
  305. *
  306. * @param int Unix timestamp
  307. * @param string timezone
  308. * @param bool whether DST is active
  309. * @return int
  310. */
  311. function gmt_to_local($time = '', $timezone = 'UTC', $dst = FALSE)
  312. {
  313. if ($time === '')
  314. {
  315. return now();
  316. }
  317. $time += timezones($timezone) * 3600;
  318. return ($dst === TRUE) ? $time + 3600 : $time;
  319. }
  320. }
  321. // ------------------------------------------------------------------------
  322. if ( ! function_exists('mysql_to_unix'))
  323. {
  324. /**
  325. * Converts a MySQL Timestamp to Unix
  326. *
  327. * @param int MySQL timestamp YYYY-MM-DD HH:MM:SS
  328. * @return int Unix timstamp
  329. */
  330. function mysql_to_unix($time = '')
  331. {
  332. // We'll remove certain characters for backward compatibility
  333. // since the formatting changed with MySQL 4.1
  334. // YYYY-MM-DD HH:MM:SS
  335. $time = str_replace(array('-', ':', ' '), '', $time);
  336. // YYYYMMDDHHMMSS
  337. return mktime(
  338. substr($time, 8, 2),
  339. substr($time, 10, 2),
  340. substr($time, 12, 2),
  341. substr($time, 4, 2),
  342. substr($time, 6, 2),
  343. substr($time, 0, 4)
  344. );
  345. }
  346. }
  347. // ------------------------------------------------------------------------
  348. if ( ! function_exists('unix_to_human'))
  349. {
  350. /**
  351. * Unix to "Human"
  352. *
  353. * Formats Unix timestamp to the following prototype: 2006-08-21 11:35 PM
  354. *
  355. * @param int Unix timestamp
  356. * @param bool whether to show seconds
  357. * @param string format: us or euro
  358. * @return string
  359. */
  360. function unix_to_human($time = '', $seconds = FALSE, $fmt = 'us')
  361. {
  362. $r = date('Y', $time).'-'.date('m', $time).'-'.date('d', $time).' ';
  363. if ($fmt === 'us')
  364. {
  365. $r .= date('h', $time).':'.date('i', $time);
  366. }
  367. else
  368. {
  369. $r .= date('H', $time).':'.date('i', $time);
  370. }
  371. if ($seconds)
  372. {
  373. $r .= ':'.date('s', $time);
  374. }
  375. if ($fmt === 'us')
  376. {
  377. return $r.' '.date('A', $time);
  378. }
  379. return $r;
  380. }
  381. }
  382. // ------------------------------------------------------------------------
  383. if ( ! function_exists('human_to_unix'))
  384. {
  385. /**
  386. * Convert "human" date to GMT
  387. *
  388. * Reverses the above process
  389. *
  390. * @param string format: us or euro
  391. * @return int
  392. */
  393. function human_to_unix($datestr = '')
  394. {
  395. if ($datestr === '')
  396. {
  397. return FALSE;
  398. }
  399. $datestr = preg_replace('/\040+/', ' ', trim($datestr));
  400. if ( ! preg_match('/^(\d{2}|\d{4})\-[0-9]{1,2}\-[0-9]{1,2}\s[0-9]{1,2}:[0-9]{1,2}(?::[0-9]{1,2})?(?:\s[AP]M)?$/i', $datestr))
  401. {
  402. return FALSE;
  403. }
  404. sscanf($datestr, '%d-%d-%d %s %s', $year, $month, $day, $time, $ampm);
  405. sscanf($time, '%d:%d:%d', $hour, $min, $sec);
  406. isset($sec) OR $sec = 0;
  407. if (isset($ampm))
  408. {
  409. $ampm = strtolower($ampm);
  410. if ($ampm[0] === 'p' && $hour < 12)
  411. {
  412. $hour += 12;
  413. }
  414. elseif ($ampm[0] === 'a' && $hour === 12)
  415. {
  416. $hour = 0;
  417. }
  418. }
  419. return mktime($hour, $min, $sec, $month, $day, $year);
  420. }
  421. }
  422. // ------------------------------------------------------------------------
  423. if ( ! function_exists('nice_date'))
  424. {
  425. /**
  426. * Turns many "reasonably-date-like" strings into something
  427. * that is actually useful. This only works for dates after unix epoch.
  428. *
  429. * @deprecated 3.1.3 Use DateTime::createFromFormat($input_format, $input)->format($output_format);
  430. * @param string The terribly formatted date-like string
  431. * @param string Date format to return (same as php date function)
  432. * @return string
  433. */
  434. function nice_date($bad_date = '', $format = FALSE)
  435. {
  436. if (empty($bad_date))
  437. {
  438. return 'Unknown';
  439. }
  440. elseif (empty($format))
  441. {
  442. $format = 'U';
  443. }
  444. // Date like: YYYYMM
  445. if (preg_match('/^\d{6}$/i', $bad_date))
  446. {
  447. if (in_array(substr($bad_date, 0, 2), array('19', '20')))
  448. {
  449. $year = substr($bad_date, 0, 4);
  450. $month = substr($bad_date, 4, 2);
  451. }
  452. else
  453. {
  454. $month = substr($bad_date, 0, 2);
  455. $year = substr($bad_date, 2, 4);
  456. }
  457. return date($format, strtotime($year.'-'.$month.'-01'));
  458. }
  459. // Date Like: YYYYMMDD
  460. if (preg_match('/^\d{8}$/i', $bad_date, $matches))
  461. {
  462. return DateTime::createFromFormat('Ymd', $bad_date)->format($format);
  463. }
  464. // Date Like: MM-DD-YYYY __or__ M-D-YYYY (or anything in between)
  465. if (preg_match('/^(\d{1,2})-(\d{1,2})-(\d{4})$/i', $bad_date, $matches))
  466. {
  467. return date($format, strtotime($matches[3].'-'.$matches[1].'-'.$matches[2]));
  468. }
  469. // Any other kind of string, when converted into UNIX time,
  470. // produces "0 seconds after epoc..." is probably bad...
  471. // return "Invalid Date".
  472. if (date('U', strtotime($bad_date)) === '0')
  473. {
  474. return 'Invalid Date';
  475. }
  476. // It's probably a valid-ish date format already
  477. return date($format, strtotime($bad_date));
  478. }
  479. }
  480. // ------------------------------------------------------------------------
  481. if ( ! function_exists('timezone_menu'))
  482. {
  483. /**
  484. * Timezone Menu
  485. *
  486. * Generates a drop-down menu of timezones.
  487. *
  488. * @param string timezone
  489. * @param string classname
  490. * @param string menu name
  491. * @param mixed attributes
  492. * @return string
  493. */
  494. function timezone_menu($default = 'UTC', $class = '', $name = 'timezones', $attributes = '')
  495. {
  496. $CI =& get_instance();
  497. $CI->lang->load('date');
  498. $default = ($default === 'GMT') ? 'UTC' : $default;
  499. $menu = '<select name="'.$name.'"';
  500. if ($class !== '')
  501. {
  502. $menu .= ' class="'.$class.'"';
  503. }
  504. $menu .= _stringify_attributes($attributes).">\n";
  505. foreach (timezones() as $key => $val)
  506. {
  507. $selected = ($default === $key) ? ' selected="selected"' : '';
  508. $menu .= '<option value="'.$key.'"'.$selected.'>'.$CI->lang->line($key)."</option>\n";
  509. }
  510. return $menu.'</select>';
  511. }
  512. }
  513. // ------------------------------------------------------------------------
  514. if ( ! function_exists('timezones'))
  515. {
  516. /**
  517. * Timezones
  518. *
  519. * Returns an array of timezones. This is a helper function
  520. * for various other ones in this library
  521. *
  522. * @param string timezone
  523. * @return string
  524. */
  525. function timezones($tz = '')
  526. {
  527. // Note: Don't change the order of these even though
  528. // some items appear to be in the wrong order
  529. $zones = array(
  530. 'UM12' => -12,
  531. 'UM11' => -11,
  532. 'UM10' => -10,
  533. 'UM95' => -9.5,
  534. 'UM9' => -9,
  535. 'UM8' => -8,
  536. 'UM7' => -7,
  537. 'UM6' => -6,
  538. 'UM5' => -5,
  539. 'UM45' => -4.5,
  540. 'UM4' => -4,
  541. 'UM35' => -3.5,
  542. 'UM3' => -3,
  543. 'UM2' => -2,
  544. 'UM1' => -1,
  545. 'UTC' => 0,
  546. 'UP1' => +1,
  547. 'UP2' => +2,
  548. 'UP3' => +3,
  549. 'UP35' => +3.5,
  550. 'UP4' => +4,
  551. 'UP45' => +4.5,
  552. 'UP5' => +5,
  553. 'UP55' => +5.5,
  554. 'UP575' => +5.75,
  555. 'UP6' => +6,
  556. 'UP65' => +6.5,
  557. 'UP7' => +7,
  558. 'UP8' => +8,
  559. 'UP875' => +8.75,
  560. 'UP9' => +9,
  561. 'UP95' => +9.5,
  562. 'UP10' => +10,
  563. 'UP105' => +10.5,
  564. 'UP11' => +11,
  565. 'UP115' => +11.5,
  566. 'UP12' => +12,
  567. 'UP1275' => +12.75,
  568. 'UP13' => +13,
  569. 'UP14' => +14
  570. );
  571. if ($tz === '')
  572. {
  573. return $zones;
  574. }
  575. return isset($zones[$tz]) ? $zones[$tz] : 0;
  576. }
  577. }
  578. // ------------------------------------------------------------------------
  579. if ( ! function_exists('date_range'))
  580. {
  581. /**
  582. * Date range
  583. *
  584. * Returns a list of dates within a specified period.
  585. *
  586. * @param int unix_start UNIX timestamp of period start date
  587. * @param int unix_end|days UNIX timestamp of period end date
  588. * or interval in days.
  589. * @param mixed is_unix Specifies whether the second parameter
  590. * is a UNIX timestamp or a day interval
  591. * - TRUE or 'unix' for a timestamp
  592. * - FALSE or 'days' for an interval
  593. * @param string date_format Output date format, same as in date()
  594. * @return array
  595. */
  596. function date_range($unix_start = '', $mixed = '', $is_unix = TRUE, $format = 'Y-m-d')
  597. {
  598. if ($unix_start == '' OR $mixed == '' OR $format == '')
  599. {
  600. return FALSE;
  601. }
  602. $is_unix = ! ( ! $is_unix OR $is_unix === 'days');
  603. // Validate input and try strtotime() on invalid timestamps/intervals, just in case
  604. if ( ( ! ctype_digit((string) $unix_start) && ($unix_start = @strtotime($unix_start)) === FALSE)
  605. OR ( ! ctype_digit((string) $mixed) && ($is_unix === FALSE OR ($mixed = @strtotime($mixed)) === FALSE))
  606. OR ($is_unix === TRUE && $mixed < $unix_start))
  607. {
  608. return FALSE;
  609. }
  610. if ($is_unix && ($unix_start == $mixed OR date($format, $unix_start) === date($format, $mixed)))
  611. {
  612. return array(date($format, $unix_start));
  613. }
  614. $range = array();
  615. $from = new DateTime();
  616. $from->setTimestamp($unix_start);
  617. if ($is_unix)
  618. {
  619. $arg = new DateTime();
  620. $arg->setTimestamp($mixed);
  621. }
  622. else
  623. {
  624. $arg = (int) $mixed;
  625. }
  626. $period = new DatePeriod($from, new DateInterval('P1D'), $arg);
  627. foreach ($period as $date)
  628. {
  629. $range[] = $date->format($format);
  630. }
  631. /* If a period end date was passed to the DatePeriod constructor, it might not
  632. * be in our results. Not sure if this is a bug or it's just possible because
  633. * the end date might actually be less than 24 hours away from the previously
  634. * generated DateTime object, but either way - we have to append it manually.
  635. */
  636. if ( ! is_int($arg) && $range[count($range) - 1] !== $arg->format($format))
  637. {
  638. $range[] = $arg->format($format);
  639. }
  640. return $range;
  641. }
  642. }