PageRenderTime 76ms CodeModel.GetById 38ms RepoModel.GetById 1ms app.codeStats 0ms

/application/protected/extensions/components/DateObject.php

https://bitbucket.org/dinhtrung/yiicorecms/
PHP | 2000 lines | 1390 code | 102 blank | 508 comment | 207 complexity | 60f21099316ca70e6bcd22348d7ae2f9 MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause, CC0-1.0, BSD-2-Clause, GPL-2.0, LGPL-2.1, LGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * @file
  4. * This module will make the date API available to other modules.
  5. * Designed to provide a light but flexible assortment of functions
  6. * and constants, with more functionality in additional files that
  7. * are not loaded unless other modules specifically include them.
  8. */
  9. /**
  10. * Set up some constants.
  11. *
  12. * Includes standard date types, format strings, strict regex strings for ISO
  13. * and DATETIME formats (seconds are optional).
  14. *
  15. * The loose regex will find any variety of ISO date and time, with or
  16. * without time, with or without dashes and colons separating the elements,
  17. * and with either a 'T' or a space separating date and time.
  18. */
  19. define('DATE_ISO', 'date');
  20. define('DATE_UNIX', 'datestamp');
  21. define('DATE_DATETIME', 'datetime');
  22. define('DATE_ARRAY', 'array');
  23. define('DATE_OBJECT', 'object');
  24. define('DATE_ICAL', 'ical');
  25. define('DATE_FORMAT_ISO', "Y-m-d\TH:i:s");
  26. define('DATE_FORMAT_UNIX', "U");
  27. define('DATE_FORMAT_DATETIME', "Y-m-d H:i:s");
  28. define('DATE_FORMAT_ICAL', "Ymd\THis");
  29. define('DATE_FORMAT_ICAL_DATE', "Ymd");
  30. define('DATE_FORMAT_DATE', 'Y-m-d');
  31. define('DATE_REGEX_ISO', '/(\d{4})?(-(\d{2}))?(-(\d{2}))?([T\s](\d{2}))?(:(\d{2}))?(:(\d{2}))?/');
  32. define('DATE_REGEX_DATETIME', '/(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):?(\d{2})?/');
  33. define('DATE_REGEX_LOOSE', '/(\d{4})-?(\d{1,2})-?(\d{1,2})([T\s]?(\d{2}):?(\d{2}):?(\d{2})?(\.\d+)?(Z|[\+\-]\d{2}:?\d{2})?)?/');
  34. define('DATE_REGEX_ICAL_DATE', '/(\d{4})(\d{2})(\d{2})/');
  35. define('DATE_REGEX_ICAL_DATETIME', '/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?/');
  36. /**
  37. * Core DateTime extension module used for as many date operations as possible in this new version.
  38. */
  39. /**
  40. * Extend PHP DateTime class with granularity handling, merge functionality and
  41. * slightly more flexible initialization parameters.
  42. *
  43. * This class is a Drupal independent extension of the >= PHP 5.2 DateTime
  44. * class.
  45. *
  46. * @see FeedsDateTimeElement class
  47. */
  48. class DateObject extends DateTime {
  49. public $granularity = array();
  50. public $errors = array();
  51. protected static $allgranularity = array('year', 'month', 'day', 'hour', 'minute', 'second', 'timezone');
  52. private $_serialized_time;
  53. private $_serialized_timezone;
  54. /**
  55. * Helper function to prepare the object during serialization.
  56. *
  57. * We are extending a core class and core classes cannot be serialized.
  58. *
  59. * Ref: http://bugs.php.net/41334, http://bugs.php.net/39821
  60. */
  61. public function __sleep(){
  62. $this->_serialized_time = $this->format('c');
  63. $this->_serialized_timezone = $this->getTimezone()->getName();
  64. return array('_serialized_time', '_serialized_timezone');
  65. }
  66. /**
  67. * Upon unserializing, we must re-build ourselves using local variables.
  68. */
  69. public function __wakeup() {
  70. $this->__construct($this->_serialized_time, new DateTimeZone($this->_serialized_timezone));
  71. }
  72. public function __toString() {
  73. return $this->format(DATE_FORMAT_DATETIME) . ' '. $this->getTimeZone()->getName();
  74. }
  75. /**
  76. * Overridden constructor.
  77. *
  78. * @param $time
  79. * time string, flexible format including timestamp.
  80. * @param $tz
  81. * PHP DateTimeZone object, string or NULL allowed, defaults to site timezone.
  82. * @param $format
  83. * PHP date() type format for parsing. Doesn't support timezones; if you have a timezone, send NULL
  84. * and the default constructor method will hopefully parse it.
  85. * $format is recommended in order to use negative or large years, which php's parser fails on.
  86. */
  87. public function __construct($time = 'now', $tz = NULL, $format = NULL) {
  88. $this->timeOnly = FALSE;
  89. $this->dateOnly = FALSE;
  90. // Allow string timezones
  91. if (!empty($tz) && !is_object($tz)) {
  92. $tz = new DateTimeZone($tz);
  93. }
  94. // Default to the site timezone when not explicitly provided.
  95. elseif (empty($tz)) {
  96. $tz = date_default_timezone_object();
  97. }
  98. // Special handling for Unix timestamps expressed in the local timezone.
  99. // Create a date object in UTC and convert it to the local timezone.
  100. // Don't try to turn things like '2010' with a format of 'Y' into a timestamp.
  101. if (is_numeric($time) && (empty($format) || $format == 'U')) {
  102. // Assume timestamp.
  103. $time = "@". $time;
  104. $date = new DateObject($time, 'UTC');
  105. if ($tz->getName() != 'UTC') {
  106. $date->setTimezone($tz);
  107. }
  108. $time = $date->format(DATE_FORMAT_DATETIME);
  109. $format = DATE_FORMAT_DATETIME;
  110. }
  111. if (is_array($time)) {
  112. // Assume we were passed an indexed array.
  113. if (empty($time['year']) && empty($time['month']) && empty($time['day'])) {
  114. $this->timeOnly = TRUE;
  115. }
  116. if (empty($time['hour']) && empty($time['minute']) && empty($time['second'])) {
  117. $this->dateOnly = TRUE;
  118. }
  119. $this->errors = $this->arrayErrors($time);
  120. // Make this into an ISO date,
  121. // forcing a full ISO date even if some values are missing.
  122. $time = $this->toISO($time, TRUE);
  123. // We checked for errors already, skip the step of parsing the input values.
  124. $format = NULL;
  125. }
  126. // The parse function will also set errors on the date parts.
  127. if (!empty($format)) {
  128. $arg = self::$allgranularity;
  129. $element = array_pop($arg);
  130. while(!$this->parse($time, $tz, $format) && $element != 'year') {
  131. $element = array_pop($arg);
  132. $format = date_limit_format($format, $arg);
  133. }
  134. if ($element == 'year') {
  135. return FALSE;
  136. }
  137. }
  138. elseif (is_string($time)) {
  139. // PHP < 5.3 doesn't like the GMT- notation for parsing timezones.
  140. $time = str_replace("GMT-", "-", $time);
  141. $time = str_replace("GMT+", "+", $time);
  142. // We are going to let the parent dateObject do a best effort attempt to turn this
  143. // string into a valid date. It might fail and we want to control the error messages.
  144. try {
  145. @parent::__construct($time, $tz);
  146. }
  147. catch (Exception $e) {
  148. $this->errors['date'] = $e;
  149. return;
  150. }
  151. $this->setGranularityFromTime($time, $tz);
  152. }
  153. // This tz was given as just an offset, which causes problems,
  154. // or the timezone was invalid.
  155. if (!$this->getTimezone() || !preg_match('/[a-zA-Z]/', $this->getTimezone()->getName())) {
  156. $this->setTimezone(new DateTimeZone("UTC"));
  157. }
  158. }
  159. /**
  160. * This function will keep this object's values by default.
  161. */
  162. public function merge(FeedsDateTime $other) {
  163. $other_tz = $other->getTimezone();
  164. $this_tz = $this->getTimezone();
  165. // Figure out which timezone to use for combination.
  166. $use_tz = ($this->hasGranularity('timezone') || !$other->hasGranularity('timezone')) ? $this_tz : $other_tz;
  167. $this2 = clone $this;
  168. $this2->setTimezone($use_tz);
  169. $other->setTimezone($use_tz);
  170. $val = $this2->toArray(TRUE);
  171. $otherval = $other->toArray();
  172. foreach (self::$allgranularity as $g) {
  173. if ($other->hasGranularity($g) && !$this2->hasGranularity($g)) {
  174. // The other class has a property we don't; steal it.
  175. $this2->addGranularity($g);
  176. $val[$g] = $otherval[$g];
  177. }
  178. }
  179. $other->setTimezone($other_tz);
  180. $this2->setDate($val['year'], $val['month'], $val['day']);
  181. $this2->setTime($val['hour'], $val['minute'], $val['second']);
  182. return $this2;
  183. }
  184. /**
  185. * Overrides default DateTime function. Only changes output values if
  186. * actually had time granularity. This should be used as a "converter" for
  187. * output, to switch tzs.
  188. *
  189. * In order to set a timezone for a datetime that doesn't have such
  190. * granularity, merge() it with one that does.
  191. */
  192. public function setTimezone($tz, $force = FALSE) {
  193. // PHP 5.2.6 has a fatal error when setting a date's timezone to itself.
  194. // http://bugs.php.net/bug.php?id=45038
  195. if (version_compare(PHP_VERSION, '5.2.7', '<') && $tz == $this->getTimezone()) {
  196. $tz = new DateTimeZone($tz->getName());
  197. }
  198. if (!$this->hasTime() || !$this->hasGranularity('timezone') || $force) {
  199. // this has no time or timezone granularity, so timezone doesn't mean much
  200. // We set the timezone using the method, which will change the day/hour, but then we switch back
  201. $arr = $this->toArray(TRUE);
  202. parent::setTimezone($tz);
  203. $this->setDate($arr['year'], $arr['month'], $arr['day']);
  204. $this->setTime($arr['hour'], $arr['minute'], $arr['second']);
  205. $this->addGranularity('timezone');
  206. return;
  207. }
  208. return parent::setTimezone($tz);
  209. }
  210. /**
  211. * Overrides base format function, formats this date according to its available granularity,
  212. * unless $force'ed not to limit to granularity.
  213. *
  214. * @TODO Incorporate translation into this so translated names will be provided.
  215. */
  216. public function format($format, $force = FALSE) {
  217. return parent::format($force ? $format : date_limit_format($format, $this->granularity));
  218. }
  219. /**
  220. * Safely adds a granularity entry to the array.
  221. */
  222. public function addGranularity($g) {
  223. $this->granularity[] = $g;
  224. $this->granularity = array_unique($this->granularity);
  225. }
  226. /**
  227. * Removes a granularity entry from the array.
  228. */
  229. public function removeGranularity($g) {
  230. if ($key = array_search($g, $this->granularity)) {
  231. unset($this->granularity[$key]);
  232. }
  233. }
  234. /**
  235. * Checks granularity array for a given entry.
  236. * Accepts an array, in which case all items must be present (AND's the query)
  237. */
  238. public function hasGranularity($g = NULL) {
  239. if ($g === NULL) {
  240. //just want to know if it has something valid
  241. //means no lower granularities without higher ones
  242. $last = TRUE;
  243. foreach(self::$allgranularity AS $arg) {
  244. if($arg == 'timezone') {
  245. continue;
  246. }
  247. if(in_array($arg, $this->granularity) && !$last) {
  248. return FALSE;
  249. }
  250. $last = in_array($arg, $this->granularity);
  251. }
  252. return in_array('year', $this->granularity);
  253. }
  254. if (is_array($g)) {
  255. foreach($g as $gran) {
  256. if (!in_array($gran, $this->granularity)) {
  257. return FALSE;
  258. }
  259. }
  260. return TRUE;
  261. }
  262. return in_array($g, $this->granularity);
  263. }
  264. // whether a date is valid for a given $granularity array, depending on if it's allowed to be flexible.
  265. public function validGranularity($granularity = NULL, $flexible = FALSE) {
  266. return $this->hasGranularity() && (!$granularity || $flexible || $this->hasGranularity($granularity));
  267. }
  268. /**
  269. * Returns whether this object has time set. Used primarily for timezone
  270. * conversion and formatting.
  271. */
  272. public function hasTime() {
  273. return $this->hasGranularity('hour');
  274. }
  275. /**
  276. * Returns whether the input values included a year.
  277. * Useful to use pseudo date objects when we only are interested in the time.
  278. */
  279. public function completeDate() {
  280. return $this->completeDate;
  281. }
  282. /**
  283. * In common usage we should not unset timezone through this.
  284. */
  285. public function limitGranularity($gran) {
  286. foreach($this->granularity AS $key => $val){
  287. if ($val != 'timezone' && !in_array($val, $gran)) {
  288. unset($this->granularity[$key]);
  289. }
  290. }
  291. }
  292. /**
  293. * Protected function to find the granularity given by the arguments to the
  294. * constructor.
  295. */
  296. protected function setGranularityFromTime($time, $tz) {
  297. $this->granularity = array();
  298. $temp = date_parse($time);
  299. // Special case for "now"
  300. if ($time == 'now') {
  301. $this->granularity = array('year', 'month', 'day', 'hour', 'minute', 'second');
  302. }
  303. else {
  304. // This PHP date_parse() method currently doesn't have resolution down to seconds, so if
  305. // there is some time, all will be set.
  306. foreach (self::$allgranularity AS $g) {
  307. if ((isset($temp[$g]) && is_numeric($temp[$g])) || ($g == 'timezone' && (isset($temp['zone_type']) && $temp['zone_type'] > 0))) {
  308. $this->granularity[] = $g;
  309. }
  310. }
  311. }
  312. if ($tz) {
  313. $this->addGranularity('timezone');
  314. }
  315. }
  316. protected function parse($date, $tz, $format) {
  317. $array = date_format_patterns();
  318. foreach ($array as $key => $value) {
  319. $patterns[] = "`(^|[^\\\\\\\\])" . $key . "`"; // the letter with no preceding '\'
  320. $repl1[] = '${1}(.)'; // a single character
  321. $repl2[] = '${1}(' . $value . ')'; // the
  322. }
  323. $patterns[] = "`\\\\\\\\([" . implode(array_keys($array)) . "])`";
  324. $repl1[] = '${1}';
  325. $repl2[] = '${1}';
  326. $format_regexp = preg_quote($format);
  327. // extract letters
  328. $regex1 = preg_replace($patterns, $repl1, $format_regexp, 1);
  329. $regex1 = str_replace('A', '(.)', $regex1);
  330. $regex1 = str_replace('a', '(.)', $regex1);
  331. preg_match('`^' . $regex1 . '$`', stripslashes($format), $letters);
  332. array_shift($letters);
  333. // extract values
  334. $regex2 = preg_replace($patterns, $repl2, $format_regexp, 1);
  335. $regex2 = str_replace('A', '(AM|PM)', $regex2);
  336. $regex2 = str_replace('a', '(am|pm)', $regex2);
  337. preg_match('`^' . $regex2 . '$`', $date, $values);
  338. array_shift($values);
  339. // if we did not find all the values for the patterns in the format, abort
  340. if (count($letters) != count($values)) {
  341. return FALSE;
  342. }
  343. $this->granularity = array();
  344. $final_date = array('hour' => 0, 'minute' => 0, 'second' => 0,
  345. 'month' => 1, 'day' => 1, 'year' => 0);
  346. foreach ($letters as $i => $letter) {
  347. $value = $values[$i];
  348. switch ($letter) {
  349. case 'd':
  350. case 'j':
  351. $final_date['day'] = intval($value);
  352. $this->addGranularity('day');
  353. break;
  354. case 'n':
  355. case 'm':
  356. $final_date['month'] = intval($value);
  357. $this->addGranularity('month');
  358. break;
  359. case 'F':
  360. $array_month_long = array_flip(date_month_names());
  361. $final_date['month'] = $array_month_long[$value];
  362. $this->addGranularity('month');
  363. break;
  364. case 'M':
  365. $array_month = array_flip(date_month_names_abbr());
  366. $final_date['month'] = $array_month[$value];
  367. $this->addGranularity('month');
  368. break;
  369. case 'Y':
  370. $final_date['year'] = $value;
  371. $this->addGranularity('year');
  372. if (strlen($value) < 4) $this->errors['year'] = t('The year is invalid. Please check that entry includes four digits.');
  373. break;
  374. case 'y':
  375. $year = $value;
  376. // if no century, we add the current one ("06" => "2006")
  377. $final_date['year'] = str_pad($year, 4, substr(date("Y"), 0, 2), STR_PAD_LEFT);
  378. $this->addGranularity('year');
  379. break;
  380. case 'a':
  381. case 'A':
  382. $ampm = strtolower($value);
  383. break;
  384. case 'g':
  385. case 'h':
  386. case 'G':
  387. case 'H':
  388. $final_date['hour'] = intval($value);
  389. $this->addGranularity('hour');
  390. break;
  391. case 'i':
  392. $final_date['minute'] = intval($value);
  393. $this->addGranularity('minute');
  394. break;
  395. case 's':
  396. $final_date['second'] = intval($value);
  397. $this->addGranularity('second');
  398. break;
  399. case 'U':
  400. parent::__construct($value, $tz ? $tz : new DateTimeZone("UTC"));
  401. $this->addGranularity('year');
  402. $this->addGranularity('month');
  403. $this->addGranularity('day');
  404. $this->addGranularity('hour');
  405. $this->addGranularity('minute');
  406. $this->addGranularity('second');
  407. return $this;
  408. break;
  409. }
  410. }
  411. if (isset($ampm) && $ampm == 'pm' && $final_date['hour'] < 12) {
  412. $final_date['hour'] += 12;
  413. }
  414. elseif (isset($ampm) && $ampm == 'am' && $final_date['hour'] == 12) {
  415. $final_date['hour'] -= 12;
  416. }
  417. // Blank becomes current time, given TZ.
  418. parent::__construct('', $tz ? $tz : new DateTimeZone("UTC"));
  419. if ($tz) {
  420. $this->addGranularity('timezone');
  421. }
  422. // SetDate expects an integer value for the year, results can
  423. // be unexpected if we feed it something like '0100' or '0000';
  424. $final_date['year'] = intval($final_date['year']);
  425. $this->errors += $this->arrayErrors($final_date);
  426. $granularity = drupal_map_assoc($this->granularity);
  427. // If the input value is '0000-00-00', PHP's date class will later incorrectly convert
  428. // it to something like '-0001-11-30' if we do setDate() here. If we don't do
  429. // setDate() here, it will default to the current date and we will lose any way to
  430. // tell that there was no date in the orignal input values. So set a flag we can use
  431. // later to tell that this date object was created using only time and that the date
  432. // values are artifical.
  433. if (empty($final_date['year']) && empty($final_date['month']) && empty($final_date['day'])) {
  434. $this->timeOnly = TRUE;
  435. }
  436. elseif (empty($this->errors)) {
  437. // setDate() expects a valid year, month, and day.
  438. // Set some defaults for dates that don't use this to
  439. // keep PHP from interpreting it as the last day of
  440. // the previous month or last month of the previous year.
  441. if (empty($granularity['month'])) {
  442. $final_date['month'] = 1;
  443. }
  444. if (empty($granularity['day'])) {
  445. $final_date['day'] = 1;
  446. }
  447. $this->setDate($final_date['year'], $final_date['month'], $final_date['day']);
  448. }
  449. if (!isset($final_date['hour']) && !isset($final_date['minute']) && !isset($final_date['second'])) {
  450. $this->dateOnly = TRUE;
  451. }
  452. elseif (empty($this->errors)) {
  453. $this->setTime($final_date['hour'], $final_date['minute'], $final_date['second']);
  454. }
  455. return $this;
  456. }
  457. /**
  458. * Helper to return all standard date parts in an array.
  459. * Will return '' for parts in which it lacks granularity.
  460. */
  461. public function toArray($force = FALSE) {
  462. return array(
  463. 'year' => $this->format('Y', $force),
  464. 'month' => $this->format('n', $force),
  465. 'day' => $this->format('j', $force),
  466. 'hour' => intval($this->format('H', $force)),
  467. 'minute' => intval($this->format('i', $force)),
  468. 'second' => intval($this->format('s', $force)),
  469. 'timezone' => $this->format('e', $force),
  470. );
  471. }
  472. /**
  473. * Create an ISO date from an array of values.
  474. */
  475. public function toISO($arr, $full = FALSE) {
  476. // Add empty values to avoid errors
  477. // The empty values must create a valid date or we will get date slippage,
  478. // i.e. a value of 2011-00-00 will get interpreted as November of 2010 by PHP.
  479. if ($full) {
  480. $arr += array('year' => 0, 'month' => 1, 'day' => 1, 'hour' => 0, 'minute' => 0, 'second' => 0);
  481. }
  482. else {
  483. $arr += array('year' => '', 'month' => '', 'day' => '', 'hour' => '', 'minute' => '', 'second' => '');
  484. }
  485. $datetime = '';
  486. if ($arr['year'] !== '') {
  487. $datetime = date_pad(intval($arr['year']), 4);
  488. if ($full || $arr['month'] !== '') {
  489. $datetime .= '-'. date_pad(intval($arr['month']));
  490. if ($full || $arr['day'] !== '') {
  491. $datetime .= '-'. date_pad(intval($arr['day']));
  492. }
  493. }
  494. }
  495. if ($arr['hour'] !== '') {
  496. $datetime .= $datetime ? 'T' : '';
  497. $datetime .= date_pad(intval($arr['hour']));
  498. if ($full || $arr['minute'] !== '') {
  499. $datetime.= ':'. date_pad(intval($arr['minute']));
  500. if ($full || $arr['second'] !== '') {
  501. $datetime .= ':'. date_pad(intval($arr['second']));
  502. }
  503. }
  504. }
  505. return $datetime;
  506. }
  507. /**
  508. * Force an incomplete date to be valid, for instance to add
  509. * a valid year, month, and day if only the time has been defined.
  510. *
  511. * @param $date
  512. * An array of date parts or a datetime string with values to be forced into date.
  513. * @param $format
  514. * The format of the date.
  515. * @param $default
  516. * 'current' - default to current day values.
  517. * 'first' - default to the first possible valid value.
  518. */
  519. public function setFuzzyDate($date, $format = NULL, $default = 'first') {
  520. $timezone = $this->getTimeZone() ? $this->getTimeZone()->getName() : NULL;
  521. $comp = new DateObject($date, $timezone, $format);
  522. $arr = $comp->toArray(TRUE);
  523. foreach ($arr as $key => $value) {
  524. // Set to intval here and then test that it is still an integer.
  525. // Needed because sometimes valid integers come through as strings.
  526. $arr[$key] = $this->forceValid($key, intval($value), $default, $arr['month'], $arr['year']);
  527. }
  528. $this->setDate($arr['year'], $arr['month'], $arr['day']);
  529. $this->setTime($arr['hour'], $arr['minute'], $arr['second']);
  530. }
  531. /**
  532. * Convert a date part into something that will produce a valid date.
  533. */
  534. protected function forceValid($part, $value, $default = 'first', $month = NULL, $year = NULL) {
  535. $now = date_now();
  536. switch ($part) {
  537. case 'year':
  538. $fallback = $now->format('Y');
  539. return !is_int($value) || empty($value) || $value < variable_get('date_min_year', 1) || $value > variable_get('date_max_year', 4000) ? $fallback : $value;
  540. break;
  541. case 'month':
  542. $fallback = $default == 'first' ? 1 : $now->format('n');
  543. return !is_int($value) || empty($value) || $value <= 0 || $value > 12 ? $fallback : $value;
  544. break;
  545. case 'day':
  546. $fallback = $default == 'first' ? 1 : $now->format('j');
  547. $max_day = isset($year) && isset($month) ? date_days_in_month($year, $month) : 31;
  548. return !is_int($value) || empty($value) || $value <= 0 || $value > $max_day ? $fallback : $value;
  549. break;
  550. case 'hour':
  551. $fallback = $default == 'first' ? 0 : $now->format('G');
  552. return !is_int($value) || $value < 0 || $value > 23 ? $fallback : $value;
  553. break;
  554. case 'minute':
  555. $fallback = $default == 'first' ? 0 : $now->format('i');
  556. return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value;
  557. break;
  558. case 'second':
  559. $fallback = $default == 'first' ? 0 : $now->format('s');
  560. return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value;
  561. break;
  562. }
  563. }
  564. // Find possible errors in an array of date part values.
  565. // The forceValid() function will change an invalid value to a valid one,
  566. // so we just need to see if the value got altered.
  567. public function arrayErrors($arr) {
  568. $errors = array();
  569. $now = date_now();
  570. $default_month = !empty($arr['month']) ? $arr['month'] : $now->format('n');
  571. $default_year = !empty($arr['year']) ? $arr['year'] : $now->format('Y');
  572. foreach ($arr as $part => $value) {
  573. // Avoid false errors when a numeric value is input as a string by forcing it numeric.
  574. $value = intval($value);
  575. if (!empty($value) && $this->forceValid($part, $value, 'now', $default_month, $default_year) != $value) {
  576. // Use a switchcase to make translation easier by providing a different message for each part.
  577. switch($part) {
  578. case 'year':
  579. $errors['year'] = t('The year is invalid.');
  580. break;
  581. case 'month':
  582. $errors['month'] = t('The month is invalid.');
  583. break;
  584. case 'day':
  585. $errors['day'] = t('The day is invalid.');
  586. break;
  587. case 'hour':
  588. $errors['hour'] = t('The hour is invalid.');
  589. break;
  590. case 'minute':
  591. $errors['minute'] = t('The minute is invalid.');
  592. break;
  593. case 'second':
  594. $errors['second'] = t('The second is invalid.');
  595. break;
  596. }
  597. }
  598. }
  599. return $errors;
  600. }
  601. /**
  602. * Compute difference between two days using a given measure.
  603. *
  604. * @param mixed $date1
  605. * the starting date
  606. * @param mixed $date2
  607. * the ending date
  608. * @param string $measure
  609. * 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'
  610. * @param string $type
  611. * the type of dates provided:
  612. * DATE_OBJECT, DATE_DATETIME, DATE_ISO, DATE_UNIX, DATE_ARRAY
  613. */
  614. public function difference($date2_in, $measure = 'seconds') {
  615. // Create cloned objects or original dates will be impacted by
  616. // the date_modify() operations done in this code.
  617. $date1 = clone($this);
  618. $date2 = clone($date2_in);
  619. if (is_object($date1) && is_object($date2)) {
  620. $diff = date_format($date2, 'U') - date_format($date1, 'U');
  621. if ($diff == 0 ) {
  622. return 0;
  623. }
  624. elseif ($diff < 0) {
  625. // Make sure $date1 is the smaller date.
  626. $temp = $date2;
  627. $date2 = $date1;
  628. $date1 = $temp;
  629. $diff = date_format($date2, 'U') - date_format($date1, 'U');
  630. }
  631. $year_diff = intval(date_format($date2, 'Y') - date_format($date1, 'Y'));
  632. switch ($measure) {
  633. // The easy cases first.
  634. case 'seconds':
  635. return $diff;
  636. case 'minutes':
  637. return $diff / 60;
  638. case 'hours':
  639. return $diff / 3600;
  640. case 'years':
  641. return $year_diff;
  642. case 'months':
  643. $format = 'n';
  644. $item1 = date_format($date1, $format);
  645. $item2 = date_format($date2, $format);
  646. if ($year_diff == 0) {
  647. return intval($item2 - $item1);
  648. }
  649. else {
  650. $item_diff = 12 - $item1;
  651. $item_diff += intval(($year_diff - 1) * 12);
  652. return $item_diff + $item2;
  653. }
  654. break;
  655. case 'days':
  656. $format = 'z';
  657. $item1 = date_format($date1, $format);
  658. $item2 = date_format($date2, $format);
  659. if ($year_diff == 0) {
  660. return intval($item2 - $item1);
  661. }
  662. else {
  663. $item_diff = date_days_in_year($date1) - $item1;
  664. for ($i = 1; $i < $year_diff; $i++) {
  665. date_modify($date1, '+1 year');
  666. $item_diff += date_days_in_year($date1);
  667. }
  668. return $item_diff + $item2;
  669. }
  670. break;
  671. case 'weeks':
  672. $week_diff = date_format($date2, 'W') - date_format($date1, 'W');
  673. $year_diff = date_format($date2, 'o') - date_format($date1, 'o');
  674. for ($i = 1; $i <= $year_diff; $i++) {
  675. date_modify($date1, '+1 year');
  676. $week_diff += date_iso_weeks_in_year($date1);
  677. }
  678. return $week_diff;
  679. }
  680. }
  681. return NULL;
  682. }
  683. }
  684. function date_db_type() {
  685. return $GLOBALS['databases']['default']['default']['driver'];
  686. }
  687. /**
  688. * Helper function for getting the format string for a date type.
  689. */
  690. function date_type_format($type) {
  691. switch ($type) {
  692. case DATE_ISO:
  693. return DATE_FORMAT_ISO;
  694. case DATE_UNIX:
  695. return DATE_FORMAT_UNIX;
  696. case DATE_DATETIME:
  697. return DATE_FORMAT_DATETIME;
  698. case DATE_ICAL:
  699. return DATE_FORMAT_ICAL;
  700. }
  701. }
  702. /**
  703. * Implement hook_init().
  704. */
  705. function date_api_init() {
  706. drupal_add_css(drupal_get_path('module', 'date_api') . '/date.css', array('weight' => CSS_THEME));
  707. }
  708. /**
  709. * An untranslated array of month names
  710. *
  711. * Needed for css, translation functions, strtotime(), and other places
  712. * that use the English versions of these words.
  713. *
  714. * @return
  715. * an array of month names
  716. */
  717. function date_month_names_untranslated() {
  718. static $month_names;
  719. if (empty($month_names)) {
  720. $month_names = array(1 => 'January', 2 => 'February', 3 => 'March',
  721. 4 => 'April', 5 => 'May', 6 => 'June', 7 => 'July',
  722. 8 => 'August', 9 => 'September', 10 => 'October',
  723. 11 => 'November', 12 => 'December');
  724. }
  725. return $month_names;
  726. }
  727. /**
  728. * Returns a translated array of month names.
  729. *
  730. * @param $required
  731. * If not required, will include a blank value at the beginning of the list.
  732. *
  733. * @return
  734. * An array of month names
  735. */
  736. function date_month_names($required = FALSE) {
  737. $month_names = array();
  738. foreach (date_month_names_untranslated() as $key => $month) {
  739. $month_names[$key] = t($month, array(), array('context' => 'Long month name'));
  740. }
  741. $none = array('' => '');
  742. return !$required ? $none + $month_names : $month_names;
  743. }
  744. /**
  745. * A translated array of month name abbreviations
  746. *
  747. * @param $required
  748. * If not required, will include a blank value at the beginning of the list.
  749. * @return
  750. * an array of month abbreviations
  751. */
  752. function date_month_names_abbr($required = FALSE, $length = 3) {
  753. $month_names = array();
  754. foreach (date_month_names_untranslated() as $key => $month) {
  755. $month_names[$key] = t(substr($month, 0, $length), array(), array('context' => 'month_abbr'));
  756. }
  757. $none = array('' => '');
  758. return !$required ? $none + $month_names : $month_names;
  759. }
  760. /**
  761. * An untranslated array of week days
  762. *
  763. * Needed for css, translation functions, strtotime(), and other places
  764. * that use the English versions of these words.
  765. *
  766. * @return
  767. * an array of week day names
  768. */
  769. function date_week_days_untranslated($refresh = TRUE) {
  770. static $weekdays;
  771. if ($refresh || empty($weekdays)) {
  772. $weekdays = array(0 => 'Sunday', 1 => 'Monday', 2 => 'Tuesday',
  773. 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday',
  774. 6 => 'Saturday');
  775. }
  776. return $weekdays;
  777. }
  778. /**
  779. * Returns a translated array of week names.
  780. *
  781. * @param $required
  782. * If not required, will include a blank value at the beginning of the array.
  783. *
  784. * @return
  785. * An array of week day names
  786. */
  787. function date_week_days($required = FALSE, $refresh = TRUE) {
  788. $weekdays = array();
  789. foreach (date_week_days_untranslated() as $key => $day) {
  790. $weekdays[$key] = t($day, array(), array('context' => ''));
  791. }
  792. $none = array('' => '');
  793. return !$required ? $none + $weekdays : $weekdays;
  794. }
  795. /**
  796. * An translated array of week day abbreviations.
  797. *
  798. * @param $required
  799. * If not required, will include a blank value at the beginning of the array.
  800. * @return
  801. * an array of week day abbreviations
  802. */
  803. function date_week_days_abbr($required = FALSE, $refresh = TRUE, $length = 3) {
  804. $weekdays = array();
  805. switch ($length) {
  806. case 1:
  807. $context = 'day_abbr1';
  808. break;
  809. case 2:
  810. $context = 'day_abbr2';
  811. break;
  812. default:
  813. $context = '';
  814. break;
  815. }
  816. foreach (date_week_days_untranslated() as $key => $day) {
  817. $weekdays[$key] = t(substr($day, 0, $length), array(), array('context' => $context));
  818. }
  819. $none = array('' => '');
  820. return !$required ? $none + $weekdays : $weekdays;
  821. }
  822. /**
  823. * Order weekdays
  824. * Correct weekdays array so first day in array matches the first day of
  825. * the week. Use to create things like calendar headers.
  826. *
  827. * @param array $weekdays
  828. * @return array
  829. */
  830. function date_week_days_ordered($weekdays) {
  831. if (variable_get('date_first_day', 1) > 0) {
  832. for ($i = 1; $i <= variable_get('date_first_day', 1); $i++) {
  833. $last = array_shift($weekdays);
  834. array_push($weekdays, $last);
  835. }
  836. }
  837. return $weekdays;
  838. }
  839. /**
  840. * An array of years.
  841. *
  842. * @param int $min
  843. * the minimum year in the array
  844. * @param int $max
  845. * the maximum year in the array
  846. * @param $required
  847. * If not required, will include a blank value at the beginning of the array.
  848. * @return
  849. * an array of years in the selected range
  850. */
  851. function date_years($min = 0, $max = 0, $required = FALSE) {
  852. // Have to be sure $min and $max are valid values;
  853. if (empty($min)) $min = intval(date('Y', REQUEST_TIME) - 3);
  854. if (empty($max)) $max = intval(date('Y', REQUEST_TIME) + 3);
  855. $none = array(0 => '');
  856. return !$required ? $none + drupal_map_assoc(range($min, $max)) : drupal_map_assoc(range($min, $max));
  857. }
  858. /**
  859. * An array of days.
  860. *
  861. * @param $required
  862. * If not required, returned array will include a blank value.
  863. * @param integer $month (optional)
  864. * @param integer $year (optional)
  865. * @return
  866. * an array of days for the selected month.
  867. */
  868. function date_days($required = FALSE, $month = NULL, $year = NULL) {
  869. // If we have a month and year, find the right last day of the month.
  870. if (!empty($month) && !empty($year)) {
  871. $date = new DateObject($year . '-' . $month . '-01 00:00:00', 'UTC');
  872. $max = $date->format('t');
  873. }
  874. // If there is no month and year given, default to 31.
  875. if (empty($max)) $max = 31;
  876. $none = array(0 => '');
  877. return !$required ? $none + drupal_map_assoc(range(1, $max)) : drupal_map_assoc(range(1, $max));
  878. }
  879. /**
  880. * An array of hours.
  881. *
  882. * @param string $format
  883. * @param $required
  884. * If not required, returned array will include a blank value.
  885. * @return
  886. * an array of hours in the selected format.
  887. */
  888. function date_hours($format = 'H', $required = FALSE) {
  889. $hours = array();
  890. if ($format == 'h' || $format == 'g') {
  891. $min = 1;
  892. $max = 12;
  893. }
  894. else {
  895. $min = 0;
  896. $max = 23;
  897. }
  898. for ($i = $min; $i <= $max; $i++) {
  899. $hours[$i] = $i < 10 && ($format == 'H' || $format == 'h') ? "0$i" : $i;
  900. }
  901. $none = array('' => '');
  902. return !$required ? $none + $hours : $hours;
  903. }
  904. /**
  905. * An array of minutes.
  906. *
  907. * @param string $format
  908. * @param $required
  909. * If not required, returned array will include a blank value.
  910. * @return
  911. * an array of minutes in the selected format.
  912. */
  913. function date_minutes($format = 'i', $required = FALSE, $increment = 1) {
  914. $minutes = array();
  915. // Have to be sure $increment has a value so we don't loop endlessly;
  916. if (empty($increment)) $increment = 1;
  917. for ($i = 0; $i < 60; $i += $increment) {
  918. $minutes[$i] = $i < 10 && $format == 'i' ? "0$i" : $i;
  919. }
  920. $none = array('' => '');
  921. return !$required ? $none + $minutes : $minutes;
  922. }
  923. /**
  924. * An array of seconds.
  925. *
  926. * @param string $format
  927. * @param $required
  928. * If not required, returned array will include a blank value.
  929. * @return array an array of seconds in the selected format.
  930. */
  931. function date_seconds($format = 's', $required = FALSE, $increment = 1) {
  932. $seconds = array();
  933. // Have to be sure $increment has a value so we don't loop endlessly;
  934. if (empty($increment)) $increment = 1;
  935. for ($i = 0; $i < 60; $i += $increment) {
  936. $seconds[$i] = $i < 10 && $format == 's' ? "0$i" : $i;
  937. }
  938. $none = array('' => '');
  939. return !$required ? $none + $seconds : $seconds;
  940. }
  941. /**
  942. * An array of am and pm options.
  943. * @param $required
  944. * If not required, returned array will include a blank value.
  945. * @return array an array of am pm options.
  946. */
  947. function date_ampm($required = FALSE) {
  948. $none = array('' => '');
  949. $ampm = array('am' => t('am', array(), array('context' => 'ampm')), 'pm' => t('pm', array(), array('context' => 'ampm')));
  950. return !$required ? $none + $ampm : $ampm;
  951. }
  952. /**
  953. * Array of regex replacement strings for date format elements.
  954. * Used to allow input in custom formats. Based on work done for
  955. * the Date module by Yves Chedemois (yched).
  956. *
  957. * @return array of date() format letters and their regex equivalents.
  958. */
  959. function date_format_patterns($strict = FALSE) {
  960. return array(
  961. 'd' => '\d{' . ($strict ? '2' : '1,2') . '}',
  962. 'm' => '\d{' . ($strict ? '2' : '1,2') . '}',
  963. 'h' => '\d{' . ($strict ? '2' : '1,2') . '}',
  964. 'H' => '\d{' . ($strict ? '2' : '1,2') . '}',
  965. 'i' => '\d{' . ($strict ? '2' : '1,2') . '}',
  966. 's' => '\d{' . ($strict ? '2' : '1,2') . '}',
  967. 'j' => '\d{1,2}', 'N' => '\d', 'S' => '\w{2}',
  968. 'w' => '\d', 'z' => '\d{1,3}', 'W' => '\d{1,2}',
  969. 'n' => '\d{1,2}', 't' => '\d{2}', 'L' => '\d', 'o' => '\d{4}',
  970. 'Y' => '-?\d{1,6}', 'y' => '\d{2}', 'B' => '\d{3}', 'g' => '\d{1,2}',
  971. 'G' => '\d{1,2}', 'e' => '\w*', 'I' => '\d', 'T' => '\w*',
  972. 'U' => '\d*', 'z' => '[+-]?\d*', 'O' => '[+-]?\d{4}',
  973. //Using S instead of w and 3 as well as 4 to pick up non-ASCII chars like German umlaute
  974. 'D' => '\S{3,4}', 'l' => '\S*', 'M' => '\S{3,4}', 'F' => '\S*',
  975. 'P' => '[+-]?\d{2}\:\d{2}',
  976. 'O' => '[+-]\d{4}',
  977. 'c' => '(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-]?\d{2}\:\d{2})',
  978. 'r' => '(\w{3}), (\d{2})\s(\w{3})\s(\d{2,4})\s(\d{2}):(\d{2}):(\d{2})([+-]?\d{4})?',
  979. );
  980. }
  981. /**
  982. * Array of granularity options and their labels
  983. *
  984. * @return array
  985. */
  986. function date_granularity_names() {
  987. return array(
  988. 'year' => t('Year', array(), array('context' => 'datetime')),
  989. 'month' => t('Month', array(), array('context' => 'datetime')),
  990. 'day' => t('Day', array(), array('context' => 'datetime')),
  991. 'hour' => t('Hour', array(), array('context' => 'datetime')),
  992. 'minute' => t('Minute', array(), array('context' => 'datetime')),
  993. 'second' => t('Second', array(), array('context' => 'datetime')),
  994. );
  995. }
  996. /**
  997. * Sort a granularity array.
  998. */
  999. function date_granularity_sorted($granularity) {
  1000. return array_intersect(array('year', 'month', 'day', 'hour', 'minute', 'second'), $granularity);
  1001. }
  1002. /**
  1003. * Give a granularity $precision, return an array of
  1004. * all the possible granularity elements.
  1005. */
  1006. function date_granularity_array_from_precision($precision) {
  1007. $granularity_array = array('year', 'month', 'day', 'hour', 'minute', 'second');
  1008. switch(($precision)) {
  1009. case 'year':
  1010. return array_slice($granularity_array, -6);
  1011. case 'month':
  1012. return array_slice($granularity_array, -5);
  1013. case 'day':
  1014. return array_slice($granularity_array, -4);
  1015. case 'hour':
  1016. return array_slice($granularity_array, -3);
  1017. case 'minute':
  1018. return array_slice($granularity_array, -2);
  1019. default:
  1020. return $granularity_array;
  1021. }
  1022. }
  1023. /**
  1024. * Give a granularity array, return the highest precision.
  1025. */
  1026. function date_granularity_precision($granularity_array) {
  1027. $input = clone($granularity_array);
  1028. return array_pop($input);
  1029. }
  1030. /**
  1031. * Construct an appropriate DATETIME format string for the granularity of an item.
  1032. */
  1033. function date_granularity_format($granularity) {
  1034. if (is_array($granularity)) {
  1035. $granularity = date_granularity_precision($granularity);
  1036. }
  1037. $format = 'Y-m-d H:i:s';
  1038. switch ($granularity) {
  1039. case 'year':
  1040. return substr($format, 0, 1);
  1041. case 'month':
  1042. return substr($format, 0, 3);
  1043. case 'day':
  1044. return substr($format, 0, 5);
  1045. case 'hour';
  1046. return substr($format, 0, 7);
  1047. case 'minute':
  1048. return substr($format, 0, 9);
  1049. default:
  1050. return $format;
  1051. }
  1052. }
  1053. /**
  1054. * A translated array of timezone names.
  1055. * Cache the untranslated array, make the translated array a static variable.
  1056. *
  1057. * @param $required
  1058. * If not required, returned array will include a blank value.
  1059. * @return
  1060. * an array of timezone names
  1061. */
  1062. function date_timezone_names($required = FALSE, $refresh = FALSE) {
  1063. static $zonenames;
  1064. if (empty($zonenames) || $refresh) {
  1065. $cached = cache_get('date_timezone_identifiers_list');
  1066. $zonenames = !empty($cached) ? $cached->data : array();
  1067. if ($refresh || empty($cached) || empty($zonenames)) {
  1068. $data = timezone_identifiers_list();
  1069. asort($data);
  1070. foreach ($data as $delta => $zone) {
  1071. // Because many time zones exist in PHP only for backward
  1072. // compatibility reasons and should not be used, the list is
  1073. // filtered by a regular expression.
  1074. if (preg_match('!^((Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/|UTC$)!', $zone)) {
  1075. $zonenames[$zone] = $zone;
  1076. }
  1077. }
  1078. if (!empty($zonenames)) {
  1079. cache_set('date_timezone_identifiers_list', $zonenames);
  1080. }
  1081. }
  1082. foreach ($zonenames as $zone) {
  1083. $zonenames[$zone] = t('!timezone', array('!timezone' => $zone));
  1084. }
  1085. }
  1086. $none = array('' => '');
  1087. return !$required ? $none + $zonenames : $zonenames;
  1088. }
  1089. /**
  1090. * An array of timezone abbreviations that the system allows.
  1091. * Cache an array of just the abbreviation names because the
  1092. * whole timezone_abbreviations_list is huge so we don't want
  1093. * to get it more than necessary.
  1094. *
  1095. * @return array
  1096. */
  1097. function date_timezone_abbr($refresh = FALSE) {
  1098. $cached = cache_get('date_timezone_abbreviations');
  1099. $data = isset($cached->data) ? $cached->data : array();
  1100. if (empty($data) || $refresh) {
  1101. $data = array_keys(timezone_abbreviations_list());
  1102. cache_set('date_timezone_abbreviations', $data);
  1103. }
  1104. return $data;
  1105. }
  1106. /**
  1107. * Reworked from Drupal's format_date function to handle pre-1970 and
  1108. * post-2038 dates and accept a date object instead of a timestamp as input.
  1109. *
  1110. * Translates formatted date results, unlike PHP function date_format().
  1111. * Should only be used for display, not input, because it can't be parsed.
  1112. *
  1113. * @param $oject
  1114. * A date object.
  1115. * @param $type
  1116. * The format to use. Can be "small", "medium" or "large" for the preconfigured
  1117. * date formats. If "custom" is specified, then $format is required as well.
  1118. * @param $format
  1119. * A PHP date format string as required by date(). A backslash should be used
  1120. * before a character to avoid interpreting the character as part of a date
  1121. * format.
  1122. * @return
  1123. * A translated date string in the requested format.
  1124. */
  1125. function date_format_date($date, $type = 'medium', $format = '', $langcode = NULL) {
  1126. if (empty($date)) {
  1127. return '';
  1128. }
  1129. switch ($type) {
  1130. case 'small':
  1131. case 'short':
  1132. $format = variable_get('date_format_short', 'm/d/Y - H:i');
  1133. break;
  1134. case 'large':
  1135. case 'long':
  1136. $format = variable_get('date_format_long', 'l, F j, Y - H:i');
  1137. break;
  1138. case 'custom':
  1139. $format = $format;
  1140. break;
  1141. case 'medium':
  1142. default:
  1143. $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
  1144. }
  1145. $format = date_limit_format($format, $date->granularity);
  1146. $max = strlen($format);
  1147. $datestring = '';
  1148. for ($i = 0; $i < $max; $i++) {
  1149. $c = $format[$i];
  1150. switch ($c) {
  1151. case 'l':
  1152. $datestring .= t($date->format('l'), array(), array('context' => '', 'langcode' => $langcode));
  1153. break;
  1154. case 'D':
  1155. $datestring .= t($date->format('D'), array(), array('context' => '', 'langcode' => $langcode));
  1156. break;
  1157. case 'F':
  1158. $datestring .= t($date->format('F'), array(), array('context' => 'Long month name', 'langcode' => $langcode));
  1159. break;
  1160. case 'M':
  1161. $datestring .= t($date->format('M'), array(), array('context' => 'month_abbr', 'langcode' => $langcode));
  1162. break;
  1163. case 'A':
  1164. case 'a':
  1165. $datestring .= t($date->format($c), array(), array('context' => 'ampm', 'langcode' => $langcode));
  1166. break;
  1167. // The timezone name translations can use t().
  1168. case 'e':
  1169. case 'T':
  1170. $datestring .= t($date->format($c));
  1171. break;
  1172. // Remaining date parts need no translation.
  1173. case 'O':
  1174. $datestring .= sprintf('%s%02d%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60);
  1175. break;
  1176. case 'P':
  1177. $datestring .= sprintf('%s%02d:%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60);
  1178. break;
  1179. case 'Z':
  1180. $datestring .= date_offset_get($date);
  1181. break;
  1182. case '\\':
  1183. $datestring .= $format[++$i];
  1184. break;
  1185. case 'r':
  1186. $datestring .= date_format_date($date, 'custom', 'D, d M Y H:i:s O', $langcode);
  1187. break;
  1188. default:
  1189. if (strpos('BdcgGhHiIjLmnNosStTuUwWYyz', $c) !== FALSE) {
  1190. $datestring .= $date->format($c);
  1191. }
  1192. else {
  1193. $datestring .= $c;
  1194. }
  1195. }
  1196. }
  1197. return $datestring;
  1198. }
  1199. /**
  1200. * An override for interval formatting that adds past and future context
  1201. *
  1202. * @param DateTime $date
  1203. * @param integer $granularity
  1204. * @return formatted string
  1205. */
  1206. function date_format_interval($date, $granularity = 2) {
  1207. // If no date is sent, then return nothing
  1208. if (empty($date)) {
  1209. return NULL;
  1210. }
  1211. $interval = REQUEST_TIME - $date->format('U');
  1212. if ($interval > 0 ) {
  1213. return t('!time ago', array('!time' => format_interval($interval, $granularity)));
  1214. }
  1215. else {
  1216. return format_interval(abs($interval), $granularity);
  1217. }
  1218. }
  1219. /**
  1220. * A date object for the current time.
  1221. *
  1222. * @param $timezone
  1223. * Optional method to force time to a specific timezone,
  1224. * defaults to user timezone, if set, otherwise site timezone.
  1225. * @return object date
  1226. */
  1227. function date_now($timezone = NULL) {
  1228. return new DateObject('now', $timezone);
  1229. }
  1230. function date_timezone_is_valid($timezone) {
  1231. static $timezone_names;
  1232. if (empty($timezone_names)) {
  1233. $timezone_names = array_keys(date_timezone_names(TRUE));
  1234. }
  1235. if (!in_array($timezone, $timezone_names)) {
  1236. return FALSE;
  1237. }
  1238. return TRUE;
  1239. }
  1240. /**
  1241. * Return a timezone name to use as a default.
  1242. *
  1243. * @return a timezone name
  1244. * Identify the default timezone for a user, if available, otherwise the site.
  1245. * Must return a value even if no timezone info has been set up.
  1246. */
  1247. function date_default_timezone($check_user = TRUE) {
  1248. global $user;
  1249. if ($check_user && variable_get('configurable_timezones', 1) && !empty($user->timezone)) {
  1250. return $user->timezone;
  1251. }
  1252. else {
  1253. $default = variable_get('date_default_timezone', '');
  1254. return empty($default) ? 'UTC' : $default;
  1255. }
  1256. }
  1257. /**
  1258. * A timezone object for the default timezone.
  1259. *
  1260. * @return a timezone name
  1261. * Identify the default timezone for a user, if available, otherwise the site.
  1262. */
  1263. function date_default_timezone_object($check_user = TRUE) {
  1264. $timezone = date_default_timezone($check_user);
  1265. return timezone_open(date_default_timezone($check_user));
  1266. }
  1267. /**
  1268. * Identify the number of days in a month for a date.
  1269. */
  1270. function date_days_in_month($year, $month) {
  1271. // Pick a day in the middle of the month to avoid timezone shifts.
  1272. $datetime = date_pad($year, 4) . '-' . date_pad($month) . '-15 00:00:00';
  1273. $date = new DateObject($datetime);
  1274. return $date->format('t');
  1275. }
  1276. /**
  1277. * Identify the number of days in a year for a date.
  1278. *
  1279. * @param mixed $date
  1280. * @return integer
  1281. */
  1282. function date_days_in_year($date = NULL) {
  1283. if (empty($date)) {
  1284. $date = date_now();
  1285. }
  1286. elseif (!is_object($date)) {
  1287. $date = new DateObject($date);
  1288. }
  1289. if (is_object($date)) {
  1290. if ($date->format('L')) {
  1291. return 366;
  1292. }
  1293. else {
  1294. return 365;
  1295. }
  1296. }
  1297. return NULL;
  1298. }
  1299. /**
  1300. * Identify the number of ISO weeks in a year for a date.
  1301. *
  1302. * December 28 is always in the last ISO week of the year.
  1303. *
  1304. * @param mixed $date
  1305. * @return integer
  1306. */
  1307. function date_iso_weeks_in_year($date = NULL) {
  1308. if (empty($date)) {
  1309. $date = date_now();
  1310. }
  1311. elseif (!is_object($date)) {
  1312. $date = new DateObject($date);
  1313. }
  1314. if (is_object($date)) {
  1315. date_date_set($date, $date->format('Y'), 12, 28);
  1316. return $date->format('W');
  1317. }
  1318. return NULL;
  1319. }
  1320. /**
  1321. * Returns day of week for a given date (0 = Sunday).
  1322. *
  1323. * @param mixed $date
  1324. * a date, default is current local day
  1325. * @return
  1326. * the number of the day in the week
  1327. */
  1328. function date_day_of_week($date = NULL) {
  1329. if (empty($date)) {
  1330. $date = date_now();
  1331. }
  1332. elseif (!is_object($date)) {
  1333. $date = new DateObject($date);
  1334. }
  1335. if (is_object($date)) {
  1336. return $date->format('w');
  1337. }
  1338. return NULL;
  1339. }
  1340. /**
  1341. * Returns translated name of the day of week for a given date.
  1342. *
  1343. * @param mixed $date
  1344. * a date, default is current local day
  1345. * @param string $abbr
  1346. * Whether to return the abbreviated name for that day
  1347. * @return
  1348. * the name of the day in the week for that date
  1349. */
  1350. function date_day_of_week_name($date = NULL, $abbr = TRUE) {
  1351. if (!is_object($date)) {
  1352. $date = new DateObject($date);
  1353. }
  1354. $dow = date_day_of_week($date);
  1355. $days = $abbr ? date_week_days_abbr() : date_week_days();
  1356. return $days[$dow];
  1357. }
  1358. /**
  1359. * Start and end dates for a calendar week, adjusted to use the
  1360. * chosen first day of week for this site.
  1361. */
  1362. function date_week_range($week, $year) {
  1363. if (variable_get('date_api_use_iso8601', FALSE)) {
  1364. return date_iso_week_range($week, $year);
  1365. }
  1366. $min_date = new DateObject($year . '-01-01 00:00:00');
  1367. $min_date->setTimezone(date_default_timezone_object());
  1368. // move to the right week
  1369. date_modify($min_date, '+' . strval(7 * ($week - 1)) . ' days');
  1370. // move backwards to the first day of the week
  1371. $first_day = variable_get('date_first_day', 1);
  1372. $day_wday = date_format($min_date, 'w');
  1373. date_modify($min_date, '-' . strval((7 + $day_wday - $first_day) % 7) . ' days');
  1374. // move forwards to the last day of the week
  1375. $max_date = clone($min_date);
  1376. date_modify($max_date, '+7 days');
  1377. if (date_format($min_date, 'Y') != $year) {
  1378. $min_date = new DateObject($year . '-01-01 00:00:00');
  1379. }
  1380. return array($min_date, $max_date);
  1381. }
  1382. /**
  1383. * Start and end dates for an ISO week.
  1384. */
  1385. function date_iso_week_range($week, $year) {
  1386. // Get to the last ISO week of the previous year.
  1387. $min_date = new DateObject(($year - 1) .'-12-28 00:00:00');
  1388. date_timezone_set($min_date, date_default_timezone_object());
  1389. // Find the first day of the first ISO week in the year.
  1390. date_modify($min_date, '+1 Monday');
  1391. // Jump ahead to the desired week for the beginning of the week range.
  1392. if ($week > 1) {
  1393. date_modify($min_date, '+ '. ($week - 1) .' weeks');
  1394. }
  1395. // move forwards to the last day of the week
  1396. $max_date = clone($min_date);
  1397. date_modify($max_date, '+7 days');
  1398. return array($min_date, $max_date);
  1399. }
  1400. /**
  1401. * The number of calendar weeks in a year.
  1402. *
  1403. * PHP week functions return the ISO week, not the calendar week.
  1404. *
  1405. * @param int $year
  1406. * @return int number of calendar weeks in selected year.
  1407. */
  1408. function date_weeks_in_year($year) {
  1409. $date = new DateObject(($year + 1) . '-01-01 12:00:00', 'UTC');
  1410. date_modify($date, '-1 day');
  1411. return date_week($date->format('Y-m-d'));
  1412. }
  1413. /**
  1414. * The calendar week number for a date.
  1415. *
  1416. * PHP week functions return the ISO week, not the calendar week.
  1417. *
  1418. * @param string $date, in the format Y-m-d
  1419. * @return int calendar week number.
  1420. */
  1421. function date_week($date) {
  1422. $date = substr($date, 0, 10);
  1423. $parts = explode('-', $date);
  1424. $date = new DateObject($date . ' 12:00:00', 'UTC');
  1425. // If we are using ISO weeks, this is easy.
  1426. if (variable_get

Large files files are truncated, but you can click here to view the full file