date_api.module

Tracking 7.x-2.x branch
  1. drupal
    1. 5 contributions/date/date_api.module
    2. 6 contributions/date/date_api.module
    3. 7 contributions/date/date_api/date_api.module

This module will make the date API available to other modules. Designed to provide a light but flexible assortment of functions and constants, with more functionality in additional files that are not loaded unless other modules specifically include them.

Classes

NameDescription
DateObjectExtend PHP DateTime class with granularity handling, merge functionality and slightly more flexible initialization parameters.

Functions & methods

NameDescription
date_ampmConstructs an array of AM and PM options.
date_api_database_infoTemporary helper to re-create equivalent of content_database_info().
date_api_element_infoImplements hook_element_info().
date_api_form_system_regional_settings_alterImplements hook_form_FORM_ID_alter() for system_regional_settings().
date_api_form_system_settings_validateValidate that the option to use ISO weeks matches first day of week choice.
date_api_menuImplements hook_menu().
date_api_statusHelper function to retun the status of required date variables.
date_api_themeImplements hook_theme().
date_daysConstructs an array of days in a month.
date_days_in_monthIdentifies the number of days in a month for a date.
date_days_in_yearIdentifies the number of days in a year for a date.
date_day_of_weekReturns day of week for a given date (0 = Sunday).
date_day_of_week_nameReturns translated name of the day of week for a given date.
date_default_timezoneReturns a timezone name to use as a default.
date_default_timezone_objectReturns a timezone object for the default timezone.
date_example_dateCreates an example date.
date_format_dateFormats a date, using a date type or a custom date format string.
date_format_intervalFormats a time interval with granularity, including past and future context.
date_format_orderConverts a format to an ordered array of granularity parts.
date_format_patternsConstructs an array of regex replacement strings for date format elements.
date_format_type_optionsCreates an array of date format types for use as an options list.
date_get_timezoneFunction to figure out which local timezone applies to a date and select it.
date_get_timezone_dbFunction to figure out which db timezone applies to a date and select it.
date_granularity_array_from_precisionConstructs an array of granularity based on a given precision.
date_granularity_formatConstructs a valid DATETIME format string for the granularity of an item.
date_granularity_namesConstructs an array of granularity options and their labels.
date_granularity_precisionGive a granularity array, return the highest precision.
date_granularity_sortedSorts a granularity array.
date_has_dateDetermines if the granularity contains a date portion.
date_has_timeDetermines if the granularity contains a time portion.
date_helpImplements hook_help().
date_hidden_elementDetermines if the date element needs to be processed.
date_hoursConstructs an array of hours.
date_increment_roundHelper function to round minutes and seconds to requested value.
date_iso_weeks_in_yearIdentifies the number of ISO weeks in a year for a date.
date_iso_week_rangeCalculates the start and end dates for an ISO week.
date_is_all_dayDetermine if a start/end date combination qualify as 'All day'.
date_is_dateDetermines if a date object is valid.
date_limit_formatLimits a date format to include only elements from a given granularity array.
date_make_iso_validThis function will replace ISO values that have the pattern 9999-00-00T00:00:00 with a pattern like 9999-01-01T00:00:00, to match the behavior of non-ISO dates and ensure that date objects created from this value contain a valid month and day. Without…
date_minutesConstructs an array of minutes.
date_month_namesReturns a translated array of month names.
date_month_names_abbrConstructs a translated array of month name abbreviations
date_month_names_untranslatedConstructs an untranslated array of month names.
date_nongranularityStrips out unwanted granularity elements.
date_nowA date object for the current time.
date_orderCreates an array of ordered strings, using English text when possible.
date_order_translatedHelper function for converting back and forth from '+1' to 'First'.
date_padHelper function to left pad date parts with zeros.
date_part_formatHelper function to get a format for a specific part of a date field.
date_range_stringConverts a min and max year into a string like '-3:+1'.
date_range_validTests validity of a date range string.
date_range_yearsSplits a string like -3:+3 or 2001:2010 into an array of min and max years.
date_secondsConstructs an array of seconds.
date_timezone_abbrReturns an array of system-allowed timezone abbreviations.
date_timezone_is_validDetermines if a timezone string is valid.
date_timezone_namesReturns a translated array of timezone names.
date_type_formatHelper function for getting the format string for a date type.
date_weekThe calendar week number for a date.
date_weeks_in_yearThe number of calendar weeks in a year.
date_week_daysReturns a translated array of week names.
date_week_days_abbrConstructs a translated array of week day abbreviations.
date_week_days_orderedReorders weekdays to match the first day of the week.
date_week_days_untranslatedConstructs an untranslated array of week days.
date_week_rangeCalculates the start and end dates for a calendar week.
date_yearsConstructs an array of years.

Constants

NameDescription
DATE_ARRAY
DATE_DATETIME
DATE_FORMAT_DATE
DATE_FORMAT_DATETIME
DATE_FORMAT_ICAL
DATE_FORMAT_ICAL_DATE
DATE_FORMAT_ISO
DATE_FORMAT_UNIX
DATE_ICAL
DATE_ISOSet up some constants.
DATE_OBJECT
DATE_REGEX_DATETIME
DATE_REGEX_ICAL_DATE
DATE_REGEX_ICAL_DATETIME
DATE_REGEX_ISO
DATE_REGEX_LOOSE
DATE_UNIX

File

View source
  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.
  38. */
  39. /**
  40. * Implements hook_help().
  41. */
  42. function date_help($path, $arg) {
  43. switch ($path) {
  44. case 'admin/help#date':
  45. $output = '';
  46. $messages = date_api_status();
  47. $output = '<h2>Date API Status</h2>';
  48. if (!empty($messages['success'])) {
  49. $output .= '<ul><li>' . implode('</li><li>', $messages['success']) . '</li></ul>';
  50. }
  51. if (!empty($messages['errors'])) {
  52. $output .= '<h3>Errors</h3><ul class="error"><li>' . implode('</li><li>', $messages['errors']) . '</li></ul>';
  53. }
  54. if (module_exists('date_tools')) {
  55. $output .= '<h3>Date Tools</h3>' . t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and with a date field. ', array('!date_wizard' => l(t('Date wizard'), 'admin/config/date/tools/date_wizard')));
  56. }
  57. else {
  58. $output .= '<h3>Date Tools</h3>' . t('Dates and calendars can be complicated to set up. If you enable the Date Tools module, it provides a Date Wizard that makes it easy to create a simple date content type with a date field. ');
  59. }
  60. $output .= '<h2>More Information</h2><p>' . t('Complete documentation for the Date and Date API modules is available at <a href="@link">http://drupal.org/node/92460</a>.', array('@link' => 'http://drupal.org/node/262062')) . '</p>';
  61. return $output;
  62. break;
  63. }
  64. }
  65. /**
  66. * Helper function to retun the status of required date variables.
  67. */
  68. function date_api_status() {
  69. $t = get_t();
  70. $error_messages = array();
  71. $success_messages = array();
  72. $value = variable_get('date_default_timezone');
  73. if (isset($value)) {
  74. $success_messages[] = $t('The timezone has been set to <a href="@regional_settings">@timezone</a>.', array('@regional_settings' => url('admin/config/regional/settings'), '@timezone' => $value));
  75. }
  76. else {
  77. $error_messages[] = $t('The Date API requires that you set up the <a href="@regional_settings">site timezone</a> to function correctly.', array('@regional_settings' => url('admin/config/regional/settings')));
  78. }
  79. $value = variable_get('date_first_day');
  80. if (isset($value)) {
  81. $days = date_week_days();
  82. $success_messages[] = $t('The first day of the week has been set to <a href="@regional_settings">@day</a>.', array('@regional_settings' => url('admin/config/regional/settings'), '@day' => $days[$value]));
  83. }
  84. else {
  85. $error_messages[] = $t('The Date API requires that you set up the <a href="@regional_settings">site first day of week settings</a> to function correctly.', array('@regional_settings' => url('admin/config/regional/settings')));
  86. }
  87. $value = variable_get('date_format_medium');
  88. if (isset($value)) {
  89. $now = date_now();
  90. $success_messages[] = $t('The medium date format type has been set to to @value. You may find it helpful to add new format types like Date, Time, Month, or Year, with appropriate formats, at <a href="@regional_date_time">Date and time</a> settings.', array('@value' => $now->format($value), '@regional_date_time' => url('admin/config/regional/date-time')));
  91. }
  92. else {
  93. $error_messages[] = $t('The Date API requires that you set up the <a href="@regional_date_time">system date formats</a> to function correctly.', array('@regional_date_time' => url('admin/config/regional/date-time')));
  94. }
  95. return array('errors', $error_messages, 'success' => $success_messages);
  96. }
  97. /**
  98. * Implements hook_menu().
  99. *
  100. * Creates a 'Date API' section on the administration page for Date
  101. * modules to use for their configuration and settings.
  102. */
  103. function date_api_menu() {
  104. $items['admin/config/date'] = array(
  105. 'title' => 'Date API',
  106. 'description' => 'Settings for modules the use the Date API.',
  107. 'position' => 'left',
  108. 'weight' => -10,
  109. 'page callback' => 'system_admin_menu_block_page',
  110. 'access arguments' => array('administer site configuration'),
  111. 'file' => 'system.admin.inc',
  112. 'file path' => drupal_get_path('module', 'system'),
  113. );
  114. return $items;
  115. }
  116. /**
  117. * Extend PHP DateTime class with granularity handling, merge functionality and
  118. * slightly more flexible initialization parameters.
  119. *
  120. * This class is a Drupal independent extension of the >= PHP 5.2 DateTime
  121. * class.
  122. *
  123. * @see FeedsDateTimeElement class
  124. */
  125. class DateObject extends DateTime {
  126. public $granularity = array();
  127. public $errors = array();
  128. protected static $allgranularity = array('year', 'month', 'day', 'hour', 'minute', 'second', 'timezone');
  129. private $serializedTime;
  130. private $serializedTimezone;
  131. /**
  132. * Prepares the object during serialization.
  133. *
  134. * We are extending a core class and core classes cannot be serialized.
  135. *
  136. * @return array
  137. * Returns an array with the names of the variables that were serialized.
  138. *
  139. * @see http://bugs.php.net/41334
  140. * @see http://bugs.php.net/39821
  141. */
  142. public function __sleep() {
  143. $this->serializedTime = $this->format('c');
  144. $this->serializedTimezone = $this->getTimezone()->getName();
  145. return array('serializedTime', 'serializedTimezone');
  146. }
  147. /**
  148. * Re-builds the object using local variables.
  149. */
  150. public function __wakeup() {
  151. $this->__construct($this->serializedTime, new DateTimeZone($this->serializedTimezone));
  152. }
  153. /**
  154. * Returns the date object as a string.
  155. *
  156. * @return string
  157. * The date object formatted as a string.
  158. */
  159. public function __toString() {
  160. return $this->format(DATE_FORMAT_DATETIME) . ' ' . $this->getTimeZone()->getName();
  161. }
  162. /**
  163. * Constructs a date object.
  164. *
  165. * @param string $time
  166. * A date/time string or array. Defaults to 'now'.
  167. * @param object|string|null $tz
  168. * PHP DateTimeZone object, string or NULL allowed. Defaults to NULL.
  169. * @param string $format
  170. * PHP date() type format for parsing. Doesn't support timezones; if you
  171. * have a timezone, send NULL and the default constructor method will
  172. * hopefully parse it. $format is recommended in order to use negative or
  173. * large years, which php's parser fails on.
  174. */
  175. public function __construct($time = 'now', $tz = NULL, $format = NULL) {
  176. $this->timeOnly = FALSE;
  177. $this->dateOnly = FALSE;
  178. // Store the raw time input so it is available for validation.
  179. $this->originalTime = $time;
  180. // Allow string timezones.
  181. if (!empty($tz) && !is_object($tz)) {
  182. $tz = new DateTimeZone($tz);
  183. }
  184. // Default to the site timezone when not explicitly provided.
  185. elseif (empty($tz)) {
  186. $tz = date_default_timezone_object();
  187. }
  188. // Special handling for Unix timestamps expressed in the local timezone.
  189. // Create a date object in UTC and convert it to the local timezone. Don't
  190. // try to turn things like '2010' with a format of 'Y' into a timestamp.
  191. if (is_numeric($time) && (empty($format) || $format == 'U')) {
  192. // Assume timestamp.
  193. $time = "@" . $time;
  194. $date = new DateObject($time, 'UTC');
  195. if ($tz->getName() != 'UTC') {
  196. $date->setTimezone($tz);
  197. }
  198. $time = $date->format(DATE_FORMAT_DATETIME);
  199. $format = DATE_FORMAT_DATETIME;
  200. $this->addGranularity('timezone');
  201. }
  202. elseif (is_array($time)) {
  203. // Assume we were passed an indexed array.
  204. if (empty($time['year']) && empty($time['month']) && empty($time['day'])) {
  205. $this->timeOnly = TRUE;
  206. }
  207. if (empty($time['hour']) && empty($time['minute']) && empty($time['second'])) {
  208. $this->dateOnly = TRUE;
  209. }
  210. $this->errors = $this->arrayErrors($time);
  211. // Make this into an ISO date, forcing a full ISO date even if some values
  212. // are missing.
  213. $time = $this->toISO($time, TRUE);
  214. // We checked for errors already, skip parsing the input values.
  215. $format = NULL;
  216. }
  217. else {
  218. // Make sure dates like 2010-00-00T00:00:00 get converted to
  219. // 2010-01-01T00:00:00 before creating a date object
  220. // to avoid unintended changes in the month or day.
  221. $time = date_make_iso_valid($time);
  222. }
  223. // The parse function will also set errors on the date parts.
  224. if (!empty($format)) {
  225. $arg = self::$allgranularity;
  226. $element = array_pop($arg);
  227. while (!$this->parse($time, $tz, $format) && $element != 'year') {
  228. $element = array_pop($arg);
  229. $format = date_limit_format($format, $arg);
  230. }
  231. if ($element == 'year') {
  232. return FALSE;
  233. }
  234. }
  235. elseif (is_string($time)) {
  236. // PHP < 5.3 doesn't like the GMT- notation for parsing timezones.
  237. $time = str_replace("GMT-", "-", $time);
  238. $time = str_replace("GMT+", "+", $time);
  239. // We are going to let the parent dateObject do a best effort attempt to
  240. // turn this string into a valid date. It might fail and we want to
  241. // control the error messages.
  242. try {
  243. @parent::__construct($time, $tz);
  244. }
  245. catch (Exception $e) {
  246. $this->errors['date'] = $e;
  247. return;
  248. }
  249. if (empty($this->granularity)) {
  250. $this->setGranularityFromTime($time, $tz);
  251. }
  252. }
  253. // If this tz was given as just an offset or the timezone
  254. // was invalid, we need to do some tweaking.
  255. if (!$this->getTimezone() || !preg_match('/[a-zA-Z]/', $this->getTimezone()->getName())) {
  256. // If the timezone name is an offset and the original
  257. // $tz has a name, use it. This happens if you pass in
  258. // a date string with an offset along with a specific timezone name.
  259. if (!preg_match('/[a-zA-Z]/', $this->getTimezone()->getName()) && preg_match('/[a-zA-Z]/', $tz->getName())) {
  260. $this->setTimezone($tz);
  261. }
  262. // If we get this far, we have no information about the timezone name,
  263. // but we will get undefined index errors without any name.
  264. else {
  265. $this->setTimezone(new DateTimeZone("UTC"));
  266. $this->errors['timezone'] = t('No valid timezone name was provided.');
  267. }
  268. }
  269. }
  270. /**
  271. * Merges two date objects together using the current date values as defaults.
  272. *
  273. * @param object $other
  274. * Another date object to merge with.
  275. *
  276. * @return object
  277. * A merged date object.
  278. */
  279. public function merge(FeedsDateTime $other) {
  280. $other_tz = $other->getTimezone();
  281. $this_tz = $this->getTimezone();
  282. // Figure out which timezone to use for combination.
  283. $use_tz = ($this->hasGranularity('timezone') || !$other->hasGranularity('timezone')) ? $this_tz : $other_tz;
  284. $this2 = clone $this;
  285. $this2->setTimezone($use_tz);
  286. $other->setTimezone($use_tz);
  287. $val = $this2->toArray(TRUE);
  288. $otherval = $other->toArray();
  289. foreach (self::$allgranularity as $g) {
  290. if ($other->hasGranularity($g) && !$this2->hasGranularity($g)) {
  291. // The other class has a property we don't; steal it.
  292. $this2->addGranularity($g);
  293. $val[$g] = $otherval[$g];
  294. }
  295. }
  296. $other->setTimezone($other_tz);
  297. $this2->setDate($val['year'], $val['month'], $val['day']);
  298. $this2->setTime($val['hour'], $val['minute'], $val['second']);
  299. return $this2;
  300. }
  301. /**
  302. * Sets the time zone for the current date.
  303. *
  304. * Overrides default DateTime function. Only changes output values if
  305. * actually had time granularity. This should be used as a "converter" for
  306. * output, to switch tzs.
  307. *
  308. * In order to set a timezone for a datetime that doesn't have such
  309. * granularity, merge() it with one that does.
  310. *
  311. * @param object $tz
  312. * A timezone object.
  313. * @param bool $force
  314. * Whether or not to skip a date with no time. Defaults to FALSE.
  315. */
  316. public function setTimezone($tz, $force = FALSE) {
  317. // PHP 5.2.6 has a fatal error when setting a date's timezone to itself.
  318. // http://bugs.php.net/bug.php?id=45038
  319. if (version_compare(PHP_VERSION, '5.2.7', '<') && $tz == $this->getTimezone()) {
  320. $tz = new DateTimeZone($tz->getName());
  321. }
  322. if (!$this->hasTime() || !$this->hasGranularity('timezone') || $force) {
  323. // This has no time or timezone granularity, so timezone doesn't mean
  324. // much. We set the timezone using the method, which will change the
  325. // day/hour, but then we switch back.
  326. $arr = $this->toArray(TRUE);
  327. parent::setTimezone($tz);
  328. $this->setDate($arr['year'], $arr['month'], $arr['day']);
  329. $this->setTime($arr['hour'], $arr['minute'], $arr['second']);
  330. $this->addGranularity('timezone');
  331. return;
  332. }
  333. return parent::setTimezone($tz);
  334. }
  335. /**
  336. * Returns date formatted according to given format.
  337. *
  338. * Overrides base format function, formats this date according to its
  339. * available granularity, unless $force'ed not to limit to granularity.
  340. *
  341. * @TODO Add translation into this so translated names will be provided.
  342. *
  343. * @param string $format
  344. * A date format string.
  345. * @param bool $force
  346. * Whether or not to limit the granularity. Defaults to FALSE.
  347. *
  348. * @return string|false
  349. * Returns the formatted date string on success or FALSE on failure.
  350. */
  351. public function format($format, $force = FALSE) {
  352. return parent::format($force ? $format : date_limit_format($format, $this->granularity));
  353. }
  354. /**
  355. * Adds a granularity entry to the array.
  356. *
  357. * @param string $g
  358. * A single date part.
  359. */
  360. public function addGranularity($g) {
  361. $this->granularity[] = $g;
  362. $this->granularity = array_unique($this->granularity);
  363. }
  364. /**
  365. * Removes a granularity entry from the array.
  366. *
  367. * @param string $g
  368. * A single date part.
  369. */
  370. public function removeGranularity($g) {
  371. if ($key = array_search($g, $this->granularity)) {
  372. unset($this->granularity[$key]);
  373. }
  374. }
  375. /**
  376. * Checks granularity array for a given entry.
  377. *
  378. * @param array|null $g
  379. * An array of date parts. Defaults to NULL.
  380. *
  381. * @returns bool
  382. * TRUE if the date part is present in the date's granularity.
  383. */
  384. public function hasGranularity($g = NULL) {
  385. if ($g === NULL) {
  386. // Just want to know if it has something valid means no lower
  387. // granularities without higher ones.
  388. $last = TRUE;
  389. foreach (self::$allgranularity as $arg) {
  390. if ($arg == 'timezone') {
  391. continue;
  392. }
  393. if (in_array($arg, $this->granularity) && !$last) {
  394. return FALSE;
  395. }
  396. $last = in_array($arg, $this->granularity);
  397. }
  398. return in_array('year', $this->granularity);
  399. }
  400. if (is_array($g)) {
  401. foreach ($g as $gran) {
  402. if (!in_array($gran, $this->granularity)) {
  403. return FALSE;
  404. }
  405. }
  406. return TRUE;
  407. }
  408. return in_array($g, $this->granularity);
  409. }
  410. /**
  411. * Determines if a a date is valid for a given granularity.
  412. *
  413. * @param array|null $granularity
  414. * An array of date parts. Defaults to NULL.
  415. * @param bool $flexible
  416. * TRUE if the granuliarty is flexible, FALSE otherwise. Defaults to FALSE.
  417. *
  418. * @return bool
  419. * Whether a date is valid for a given granularity.
  420. */
  421. public function validGranularity($granularity = NULL, $flexible = FALSE) {
  422. $true = $this->hasGranularity() && (!$granularity || $flexible || $this->hasGranularity($granularity));
  423. if (!$true && $granularity) {
  424. foreach ((array) $granularity as $part) {
  425. if (!$this->hasGranularity($part)) {
  426. $this->errors[$part] = t("The @part is missing.", array('@part' => $part));
  427. }
  428. }
  429. }
  430. return $true;
  431. }
  432. /**
  433. * Returns whether this object has time set.
  434. *
  435. * Used primarily for timezone conversion and formatting.
  436. *
  437. * @return bool
  438. * TRUE if the date contains time parts, FALSE otherwise.
  439. */
  440. public function hasTime() {
  441. return $this->hasGranularity('hour');
  442. }
  443. /**
  444. * Returns whether the input values included a year.
  445. *
  446. * Useful to use pseudo date objects when we only are interested in the time.
  447. *
  448. * @todo $this->completeDate does not actually exist?
  449. */
  450. public function completeDate() {
  451. return $this->completeDate;
  452. }
  453. /**
  454. * Removes unwanted date parts from a date.
  455. *
  456. * In common usage we should not unset timezone through this.
  457. *
  458. * @param array $granularity
  459. * An array of date parts.
  460. */
  461. public function limitGranularity($granularity) {
  462. foreach ($this->granularity as $key => $val) {
  463. if ($val != 'timezone' && !in_array($val, $granularity)) {
  464. unset($this->granularity[$key]);
  465. }
  466. }
  467. }
  468. /**
  469. * Determines the granularity of a date based on the constructor's arguments.
  470. *
  471. * @param string $time
  472. * A date string.
  473. * @param bool $tz
  474. * TRUE if the date has a timezone, FALSE otherwise.
  475. */
  476. protected function setGranularityFromTime($time, $tz) {
  477. $this->granularity = array();
  478. $temp = date_parse($time);
  479. // Special case for 'now'.
  480. if ($time == 'now') {
  481. $this->granularity = array('year', 'month', 'day', 'hour', 'minute', 'second');
  482. }
  483. else {
  484. // This PHP date_parse() method currently doesn't have resolution down to
  485. // seconds, so if there is some time, all will be set.
  486. foreach (self::$allgranularity as $g) {
  487. if ((isset($temp[$g]) && is_numeric($temp[$g])) || ($g == 'timezone' && (isset($temp['zone_type']) && $temp['zone_type'] > 0))) {
  488. $this->granularity[] = $g;
  489. }
  490. }
  491. }
  492. if ($tz) {
  493. $this->addGranularity('timezone');
  494. }
  495. }
  496. /**
  497. * Converts a date string into a date object.
  498. *
  499. * @param string $date
  500. * The date string to parse.
  501. * @param object $tz
  502. * A timezone object.
  503. * @param string $format
  504. * The date format string.
  505. *
  506. * @return object
  507. * Returns the date object.
  508. */
  509. protected function parse($date, $tz, $format) {
  510. $array = date_format_patterns();
  511. foreach ($array as $key => $value) {
  512. // The letter with no preceding '\'.
  513. $patterns[] = "`(^|[^\\\\\\\\])" . $key . "`";
  514. // A single character.
  515. $repl1[] = '${1}(.)';
  516. // The.
  517. $repl2[] = '${1}(' . $value . ')';
  518. }
  519. $patterns[] = "`\\\\\\\\([" . implode(array_keys($array)) . "])`";
  520. $repl1[] = '${1}';
  521. $repl2[] = '${1}';
  522. $format_regexp = preg_quote($format);
  523. // Extract letters.
  524. $regex1 = preg_replace($patterns, $repl1, $format_regexp, 1);
  525. $regex1 = str_replace('A', '(.)', $regex1);
  526. $regex1 = str_replace('a', '(.)', $regex1);
  527. preg_match('`^' . $regex1 . '$`', stripslashes($format), $letters);
  528. array_shift($letters);
  529. // Extract values.
  530. $regex2 = preg_replace($patterns, $repl2, $format_regexp, 1);
  531. $regex2 = str_replace('A', '(AM|PM)', $regex2);
  532. $regex2 = str_replace('a', '(am|pm)', $regex2);
  533. preg_match('`^' . $regex2 . '$`', $date, $values);
  534. array_shift($values);
  535. // If we did not find all the values for the patterns in the format, abort.
  536. if (count($letters) != count($values)) {
  537. $this->errors['invalid'] = t('The value @date does not match the expected format.', array('@date' => $date));
  538. return FALSE;
  539. }
  540. $this->granularity = array();
  541. $final_date = array('hour' => 0, 'minute' => 0, 'second' => 0, 'month' => 1, 'day' => 1, 'year' => 0);
  542. foreach ($letters as $i => $letter) {
  543. $value = $values[$i];
  544. switch ($letter) {
  545. case 'd':
  546. case 'j':
  547. $final_date['day'] = intval($value);
  548. $this->addGranularity('day');
  549. break;
  550. case 'n':
  551. case 'm':
  552. $final_date['month'] = intval($value);
  553. $this->addGranularity('month');
  554. break;
  555. case 'F':
  556. $array_month_long = array_flip(date_month_names());
  557. $final_date['month'] = array_key_exists($value, $array_month_long) ? $array_month_long[$value] : -1;
  558. $this->addGranularity('month');
  559. break;
  560. case 'M':
  561. $array_month = array_flip(date_month_names_abbr());
  562. $final_date['month'] = array_key_exists($value, $array_month) ? $array_month[$value] : -1;
  563. $this->addGranularity('month');
  564. break;
  565. case 'Y':
  566. $final_date['year'] = $value;
  567. $this->addGranularity('year');
  568. if (strlen($value) < 4) {
  569. $this->errors['year'] = t('The year is invalid. Please check that entry includes four digits.');
  570. }
  571. break;
  572. case 'y':
  573. $year = $value;
  574. // If no century, we add the current one ("06" => "2006").
  575. $final_date['year'] = str_pad($year, 4, substr(date("Y"), 0, 2), STR_PAD_LEFT);
  576. $this->addGranularity('year');
  577. break;
  578. case 'a':
  579. case 'A':
  580. $ampm = strtolower($value);
  581. break;
  582. case 'g':
  583. case 'h':
  584. case 'G':
  585. case 'H':
  586. $final_date['hour'] = intval($value);
  587. $this->addGranularity('hour');
  588. break;
  589. case 'i':
  590. $final_date['minute'] = intval($value);
  591. $this->addGranularity('minute');
  592. break;
  593. case 's':
  594. $final_date['second'] = intval($value);
  595. $this->addGranularity('second');
  596. break;
  597. case 'U':
  598. parent::__construct($value, $tz ? $tz : new DateTimeZone("UTC"));
  599. $this->addGranularity('year');
  600. $this->addGranularity('month');
  601. $this->addGranularity('day');
  602. $this->addGranularity('hour');
  603. $this->addGranularity('minute');
  604. $this->addGranularity('second');
  605. return $this;
  606. break;
  607. }
  608. }
  609. if (isset($ampm) && $ampm == 'pm' && $final_date['hour'] < 12) {
  610. $final_date['hour'] += 12;
  611. }
  612. elseif (isset($ampm) && $ampm == 'am' && $final_date['hour'] == 12) {
  613. $final_date['hour'] -= 12;
  614. }
  615. // Blank becomes current time, given TZ.
  616. parent::__construct('', $tz ? $tz : new DateTimeZone("UTC"));
  617. if ($tz) {
  618. $this->addGranularity('timezone');
  619. }
  620. // SetDate expects an integer value for the year, results can be unexpected
  621. // if we feed it something like '0100' or '0000'.
  622. $final_date['year'] = intval($final_date['year']);
  623. $this->errors += $this->arrayErrors($final_date);
  624. $granularity = drupal_map_assoc($this->granularity);
  625. // If the input value is '0000-00-00', PHP's date class will later
  626. // incorrectly convert it to something like '-0001-11-30' if we do setDate()
  627. // here. If we don't do setDate() here, it will default to the current date
  628. // and we will lose any way to tell that there was no date in the orignal
  629. // input values. So set a flag we can use later to tell that this date
  630. // object was created using only time values, and that the date values are
  631. // artifical.
  632. if (empty($final_date['year']) && empty($final_date['month']) && empty($final_date['day'])) {
  633. $this->timeOnly = TRUE;
  634. }
  635. elseif (empty($this->errors)) {
  636. // setDate() expects a valid year, month, and day.
  637. // Set some defaults for dates that don't use this to
  638. // keep PHP from interpreting it as the last day of
  639. // the previous month or last month of the previous year.
  640. if (empty($granularity['month'])) {
  641. $final_date['month'] = 1;
  642. }
  643. if (empty($granularity['day'])) {
  644. $final_date['day'] = 1;
  645. }
  646. $this->setDate($final_date['year'], $final_date['month'], $final_date['day']);
  647. }
  648. if (!isset($final_date['hour']) && !isset($final_date['minute']) && !isset($final_date['second'])) {
  649. $this->dateOnly = TRUE;
  650. }
  651. elseif (empty($this->errors)) {
  652. $this->setTime($final_date['hour'], $final_date['minute'], $final_date['second']);
  653. }
  654. return $this;
  655. }
  656. /**
  657. * Returns all standard date parts in an array.
  658. *
  659. * Will return '' for parts in which it lacks granularity.
  660. *
  661. * @param bool $force
  662. * Whether or not to limit the granularity. Defaults to FALSE.
  663. *
  664. * @return array
  665. * An array of formatted date part values, keyed by date parts.
  666. */
  667. public function toArray($force = FALSE) {
  668. return array(
  669. 'year' => $this->format('Y', $force),
  670. 'month' => $this->format('n', $force),
  671. 'day' => $this->format('j', $force),
  672. 'hour' => intval($this->format('H', $force)),
  673. 'minute' => intval($this->format('i', $force)),
  674. 'second' => intval($this->format('s', $force)),
  675. 'timezone' => $this->format('e', $force),
  676. );
  677. }
  678. /**
  679. * Creates an ISO date from an array of values.
  680. *
  681. * @param array $arr
  682. * An array of date values keyed by date part.
  683. * @param bool $full
  684. * (optional) Whether to force a full date by filling in missing values.
  685. * Defaults to FALSE.
  686. */
  687. public function toISO($arr, $full = FALSE) {
  688. // Add empty values to avoid errors. The empty values must create a valid
  689. // date or we will get date slippage, i.e. a value of 2011-00-00 will get
  690. // interpreted as November of 2010 by PHP.
  691. if ($full) {
  692. $arr += array('year' => 0, 'month' => 1, 'day' => 1, 'hour' => 0, 'minute' => 0, 'second' => 0);
  693. }
  694. else {
  695. $arr += array('year' => '', 'month' => '', 'day' => '', 'hour' => '', 'minute' => '', 'second' => '');
  696. }
  697. $datetime = '';
  698. if ($arr['year'] !== '') {
  699. $datetime = date_pad(intval($arr['year']), 4);
  700. if ($full || $arr['month'] !== '') {
  701. $datetime .= '-' . date_pad(intval($arr['month']));
  702. if ($full || $arr['day'] !== '') {
  703. $datetime .= '-' . date_pad(intval($arr['day']));
  704. }
  705. }
  706. }
  707. if ($arr['hour'] !== '') {
  708. $datetime .= $datetime ? 'T' : '';
  709. $datetime .= date_pad(intval($arr['hour']));
  710. if ($full || $arr['minute'] !== '') {
  711. $datetime .= ':' . date_pad(intval($arr['minute']));
  712. if ($full || $arr['second'] !== '') {
  713. $datetime .= ':' . date_pad(intval($arr['second']));
  714. }
  715. }
  716. }
  717. return $datetime;
  718. }
  719. /**
  720. * Forces an incomplete date to be valid.
  721. *
  722. * E.g., add a valid year, month, and day if only the time has been defined.
  723. *
  724. * @param array|string $date
  725. * An array of date parts or a datetime string with values to be massaged
  726. * into a valid date object.
  727. * @param string $format
  728. * (optional) The format of the date. Defaults to NULL.
  729. * @param string $default
  730. * (optional) If the fallback should use the first value of the date part,
  731. * or the current value of the date part. Defaults to 'first'.
  732. */
  733. public function setFuzzyDate($date, $format = NULL, $default = 'first') {
  734. $timezone = $this->getTimeZone() ? $this->getTimeZone()->getName() : NULL;
  735. $comp = new DateObject($date, $timezone, $format);
  736. $arr = $comp->toArray(TRUE);
  737. foreach ($arr as $key => $value) {
  738. // Set to intval here and then test that it is still an integer.
  739. // Needed because sometimes valid integers come through as strings.
  740. $arr[$key] = $this->forceValid($key, intval($value), $default, $arr['month'], $arr['year']);
  741. }
  742. $this->setDate($arr['year'], $arr['month'], $arr['day']);
  743. $this->setTime($arr['hour'], $arr['minute'], $arr['second']);
  744. }
  745. /**
  746. * Converts a date part into something that will produce a valid date.
  747. *
  748. * @param string $part
  749. * The date part.
  750. * @param int $value
  751. * The date value for this part.
  752. * @param string $default
  753. * (optional) If the fallback should use the first value of the date part,
  754. * or the current value of the date part. Defaults to 'first'.
  755. * @param int $month
  756. * (optional) Used when the date part is less than 'month' to specify the
  757. * date. Defaults to NULL.
  758. * @param int $year
  759. * (optional) Used when the date part is less than 'year' to specify the
  760. * date. Defaults to NULL.
  761. *
  762. * @return int
  763. * A valid date value.
  764. */
  765. protected function forceValid($part, $value, $default = 'first', $month = NULL, $year = NULL) {
  766. $now = date_now();
  767. switch ($part) {
  768. case 'year':
  769. $fallback = $now->format('Y');
  770. return !is_int($value) || empty($value) || $value < variable_get('date_min_year', 1) || $value > variable_get('date_max_year', 4000) ? $fallback : $value;
  771. break;
  772. case 'month':
  773. $fallback = $default == 'first' ? 1 : $now->format('n');
  774. return !is_int($value) || empty($value) || $value <= 0 || $value > 12 ? $fallback : $value;
  775. break;
  776. case 'day':
  777. $fallback = $default == 'first' ? 1 : $now->format('j');
  778. $max_day = isset($year) && isset($month) ? date_days_in_month($year, $month) : 31;
  779. return !is_int($value) || empty($value) || $value <= 0 || $value > $max_day ? $fallback : $value;
  780. break;
  781. case 'hour':
  782. $fallback = $default == 'first' ? 0 : $now->format('G');
  783. return !is_int($value) || $value < 0 || $value > 23 ? $fallback : $value;
  784. break;
  785. case 'minute':
  786. $fallback = $default == 'first' ? 0 : $now->format('i');
  787. return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value;
  788. break;
  789. case 'second':
  790. $fallback = $default == 'first' ? 0 : $now->format('s');
  791. return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value;
  792. break;
  793. }
  794. }
  795. /**
  796. * Finds possible errors in an array of date part values.
  797. *
  798. * The forceValid() function will change an invalid value to a valid one, so
  799. * we just need to see if the value got altered.
  800. *
  801. * @param array $arr
  802. * An array of date values, keyed by date part.
  803. *
  804. * @return array
  805. * An array of error messages, keyed by date part.
  806. */
  807. public function arrayErrors($arr) {
  808. $errors = array();
  809. $now = date_now();
  810. $default_month = !empty($arr['month']) ? $arr['month'] : $now->format('n');
  811. $default_year = !empty($arr['year']) ? $arr['year'] : $now->format('Y');
  812. $this->granularity = array();
  813. foreach ($arr as $part => $value) {
  814. // Explicitly set the granularity to the values in the input array.
  815. if (is_numeric($value)) {
  816. $this->addGranularity($part);
  817. }
  818. // Avoid false errors when a numeric value is input as a string by casting
  819. // as an integer.
  820. $value = intval($value);
  821. if (!empty($value) && $this->forceValid($part, $value, 'now', $default_month, $default_year) != $value) {
  822. // Use a switch/case to make translation easier by providing a different
  823. // message for each part.
  824. switch ($part) {
  825. case 'year':
  826. $errors['year'] = t('The year is invalid.');
  827. break;
  828. case 'month':
  829. $errors['month'] = t('The month is invalid.');
  830. break;
  831. case 'day':
  832. $errors['day'] = t('The day is invalid.');
  833. break;
  834. case 'hour':
  835. $errors['hour'] = t('The hour is invalid.');
  836. break;
  837. case 'minute':
  838. $errors['minute'] = t('The minute is invalid.');
  839. break;
  840. case 'second':
  841. $errors['second'] = t('The second is invalid.');
  842. break;
  843. }
  844. }
  845. }
  846. if ($this->hasTime()) {
  847. $this->addGranularity('timezone');
  848. }
  849. return $errors;
  850. }
  851. /**
  852. * Computes difference between two days using a given measure.
  853. *
  854. * @param object $date2_in
  855. * The stop date.
  856. * @param string $measure
  857. * (optional) A granularity date part. Defaults to 'seconds'.
  858. * @param boolean $absolute
  859. * (optional) Indicate whether the absolute value of the difference should
  860. * be returned or if the sign should be retained. Defaults to TRUE.
  861. */
  862. public function difference($date2_in, $measure = 'seconds', $absolute = TRUE) {
  863. // Create cloned objects or original dates will be impacted by the
  864. // date_modify() operations done in this code.
  865. $date1 = clone($this);
  866. $date2 = clone($date2_in);
  867. if (is_object($date1) && is_object($date2)) {
  868. $diff = date_format($date2, 'U') - date_format($date1, 'U');
  869. if ($diff == 0) {
  870. return 0;
  871. }
  872. elseif ($diff < 0 && $absolute) {
  873. // Make sure $date1 is the smaller date.
  874. $temp = $date2;
  875. $date2 = $date1;
  876. $date1 = $temp;
  877. $diff = date_format($date2, 'U') - date_format($date1, 'U');
  878. }
  879. $year_diff = intval(date_format($date2, 'Y') - date_format($date1, 'Y'));
  880. switch ($measure) {
  881. // The easy cases first.
  882. case 'seconds':
  883. return $diff;
  884. case 'minutes':
  885. return $diff / 60;
  886. case 'hours':
  887. return $diff / 3600;
  888. case 'years':
  889. return $year_diff;
  890. case 'months':
  891. $format = 'n';
  892. $item1 = date_format($date1, $format);
  893. $item2 = date_format($date2, $format);
  894. if ($year_diff == 0) {
  895. return intval($item2 - $item1);
  896. }
  897. else {
  898. $item_diff = 12 - $item1;
  899. $item_diff += intval(($year_diff - 1) * 12);
  900. return $item_diff + $item2;
  901. }
  902. break;
  903. case 'days':
  904. $format = 'z';
  905. $item1 = date_format($date1, $format);
  906. $item2 = date_format($date2, $format);
  907. if ($year_diff == 0) {
  908. return intval($item2 - $item1);
  909. }
  910. else {
  911. $item_diff = date_days_in_year($date1) - $item1;
  912. for ($i = 1; $i < $year_diff; $i++) {
  913. date_modify($date1, '+1 year');
  914. $item_diff += date_days_in_year($date1);
  915. }
  916. return $item_diff + $item2;
  917. }
  918. break;
  919. case 'weeks':
  920. $week_diff = date_format($date2, 'W') - date_format($date1, 'W');
  921. $year_diff = date_format($date2, 'o') - date_format($date1, 'o');
  922. for ($i = 1; $i <= $year_diff; $i++) {
  923. date_modify($date1, '+1 year');
  924. $week_diff += date_iso_weeks_in_year($date1);
  925. }
  926. return $week_diff;
  927. }
  928. }
  929. return NULL;
  930. }
  931. }
  932. /**
  933. * Determines if the date element needs to be processed.
  934. *
  935. * Helper function to see if date element has been hidden by FAPI to see if it
  936. * needs to be processed or just pass the value through. This is needed since
  937. * normal date processing explands the date element into parts and then
  938. * reconstructs it, which is not needed or desirable if the field is hidden.
  939. *
  940. * @param array $element
  941. * The date element to check.
  942. *
  943. * @return bool
  944. * TRUE if the element is effectively hidden, FALSE otherwise.
  945. */
  946. function date_hidden_element($element) {
  947. // @TODO What else needs to be tested to see if dates are hidden or disabled?
  948. if ((isset($element['#access']) && empty($element['#access']))
  949. || !empty($element['#programmed'])
  950. || in_array($element['#type'], array('hidden', 'value'))) {
  951. return TRUE;
  952. }
  953. return FALSE;
  954. }
  955. /**
  956. * Helper function for getting the format string for a date type.
  957. *
  958. * @param string $type
  959. * A date type format name.
  960. *
  961. * @return string
  962. * A date type format, like 'Y-m-d H:i:s'.
  963. */
  964. function date_type_format($type) {
  965. switch ($type) {
  966. case DATE_ISO:
  967. return DATE_FORMAT_ISO;
  968. case DATE_UNIX:
  969. return DATE_FORMAT_UNIX;
  970. case DATE_DATETIME:
  971. return DATE_FORMAT_DATETIME;
  972. case DATE_ICAL:
  973. return DATE_FORMAT_ICAL;
  974. }
  975. }
  976. /**
  977. * Constructs an untranslated array of month names.
  978. *
  979. * Needed for CSS, translation functions, strtotime(), and other places
  980. * that use the English versions of these words.
  981. *
  982. * @return array
  983. * An array of month names.
  984. */
  985. function date_month_names_untranslated() {
  986. static $month_names;
  987. if (empty($month_names)) {
  988. $month_names = array(
  989. 1 => 'January',
  990. 2 => 'February',
  991. 3 => 'March',
  992. 4 => 'April',
  993. 5 => 'May',
  994. 6 => 'June',
  995. 7 => 'July',
  996. 8 => 'August',
  997. 9 => 'September',
  998. 10 => 'October',
  999. 11 => 'November',
  1000. 12 => 'December',
  1001. );
  1002. }
  1003. return $month_names;
  1004. }
  1005. /**
  1006. * Returns a translated array of month names.
  1007. *
  1008. * @param bool $required
  1009. * (optional) If FALSE, the returned array will include a blank value.
  1010. * Defaults to FALSE.
  1011. *
  1012. * @return array
  1013. * An array of month names.
  1014. */
  1015. function date_month_names($required = FALSE) {
  1016. $month_names = array();
  1017. foreach (date_month_names_untranslated() as $key => $month) {
  1018. $month_names[$key] = t($month, array(), array('context' => 'Long month name'));
  1019. }
  1020. $none = array('' => '');
  1021. return !$required ? $none + $month_names : $month_names;
  1022. }
  1023. /**
  1024. * Constructs a translated array of month name abbreviations
  1025. *
  1026. * @param bool $required
  1027. * (optional) If FALSE, the returned array will include a blank value.
  1028. * Defaults to FALSE.
  1029. * @param int $length
  1030. * (optional) The length of the abbreviation. Defaults to 3.
  1031. *
  1032. * @return array
  1033. * An array of month abbreviations.
  1034. */
  1035. function date_month_names_abbr($required = FALSE, $length = 3) {
  1036. $month_names = array();
  1037. foreach (date_month_names_untranslated() as $key => $month) {
  1038. if ($length == 3) {
  1039. $month_names[$key] = t(substr($month, 0, $length), array());
  1040. }
  1041. else {
  1042. $month_names[$key] = t(substr($month, 0, $length), array(), array('context' => 'month_abbr'));
  1043. }
  1044. }
  1045. $none = array('' => '');
  1046. return !$required ? $none + $month_names : $month_names;
  1047. }
  1048. /**
  1049. * Constructs an untranslated array of week days.
  1050. *
  1051. * Needed for CSS, translation functions, strtotime(), and other places
  1052. * that use the English versions of these words.
  1053. *
  1054. * @param bool $refresh
  1055. * (optional) Whether to refresh the list. Defaults to TRUE.
  1056. *
  1057. * @return array
  1058. * An array of week day names
  1059. */
  1060. function date_week_days_untranslated($refresh = TRUE) {
  1061. static $weekdays;
  1062. if ($refresh || empty($weekdays)) {
  1063. $weekdays = array(
  1064. 'Sunday',
  1065. 'Monday',
  1066. 'Tuesday',
  1067. 'Wednesday',
  1068. 'Thursday',
  1069. 'Friday',
  1070. 'Saturday',
  1071. );
  1072. }
  1073. return $weekdays;
  1074. }
  1075. /**
  1076. * Returns a translated array of week names.
  1077. *
  1078. * @param bool $required
  1079. * (optional) If FALSE, the returned array will include a blank value.
  1080. * Defaults to FALSE.
  1081. *
  1082. * @return array
  1083. * An array of week day names
  1084. */
  1085. function date_week_days($required = FALSE, $refresh = TRUE) {
  1086. $weekdays = array();
  1087. foreach (date_week_days_untranslated() as $key => $day) {
  1088. $weekdays[$key] = t($day, array(), array('context' => ''));
  1089. }
  1090. $none = array('' => '');
  1091. return !$required ? $none + $weekdays : $weekdays;
  1092. }
  1093. /**
  1094. * Constructs a translated array of week day abbreviations.
  1095. *
  1096. * @param bool $required
  1097. * (optional) If FALSE, the returned array will include a blank value.
  1098. * Defaults to FALSE.
  1099. * @param bool $refresh
  1100. * (optional) Whether to refresh the list. Defaults to TRUE.
  1101. * @param int $length
  1102. * (optional) The length of the abbreviation. Defaults to 3.
  1103. *
  1104. * @return array
  1105. * An array of week day abbreviations
  1106. */
  1107. function date_week_days_abbr($required = FALSE, $refresh = TRUE, $length = 3) {
  1108. $weekdays = array();
  1109. switch ($length) {
  1110. case 1:
  1111. $context = 'day_abbr1';
  1112. break;
  1113. case 2:
  1114. $context = 'day_abbr2';
  1115. break;
  1116. default:
  1117. $context = '';
  1118. break;
  1119. }
  1120. foreach (date_week_days_untranslated() as $key => $day) {
  1121. $weekdays[$key] = t(substr($day, 0, $length), array(), array('context' => $context));
  1122. }
  1123. $none = array('' => '');
  1124. return !$required ? $none + $weekdays : $weekdays;
  1125. }
  1126. /**
  1127. * Reorders weekdays to match the first day of the week.
  1128. *
  1129. * @param array $weekdays
  1130. * An array of weekdays.
  1131. *
  1132. * @return array
  1133. * An array of weekdays reordered to match the first day of the week.
  1134. */
  1135. function date_week_days_ordered($weekdays) {
  1136. $first_day = variable_get('date_first_day', 0);
  1137. if ($first_day > 0) {
  1138. for ($i = 1; $i <= $first_day; $i++) {
  1139. $last = array_shift($weekdays);
  1140. array_push($weekdays, $last);
  1141. }
  1142. }
  1143. return $weekdays;
  1144. }
  1145. /**
  1146. * Constructs an array of years.
  1147. *
  1148. * @param int $min
  1149. * The minimum year in the array.
  1150. * @param int $max
  1151. * The maximum year in the array.
  1152. * @param bool $required
  1153. * (optional) If FALSE, the returned array will include a blank value.
  1154. * Defaults to FALSE.
  1155. *
  1156. * @return array
  1157. * An array of years in the selected range.
  1158. */
  1159. function date_years($min = 0, $max = 0, $required = FALSE) {
  1160. // Ensure $min and $max are valid values.
  1161. if (empty($min)) {
  1162. $min = intval(date('Y', REQUEST_TIME) - 3);
  1163. }
  1164. if (empty($max)) {
  1165. $max = intval(date('Y', REQUEST_TIME) + 3);
  1166. }
  1167. $none = array(0 => '');
  1168. return !$required ? $none + drupal_map_assoc(range($min, $max)) : drupal_map_assoc(range($min, $max));
  1169. }
  1170. /**
  1171. * Constructs an array of days in a month.
  1172. *
  1173. * @param bool $required
  1174. * (optional) If FALSE, the returned array will include a blank value.
  1175. * Defaults to FALSE.
  1176. * @param int $month
  1177. * (optional) The month in which to find the number of days.
  1178. * @param int $year
  1179. * (optional) The year in which to find the number of days.
  1180. *
  1181. * @return array
  1182. * An array of days for the selected month.
  1183. */
  1184. function date_days($required = FALSE, $month = NULL, $year = NULL) {
  1185. // If we have a month and year, find the right last day of the month.
  1186. if (!empty($month) && !empty($year)) {
  1187. $date = new DateObject($year . '-' . $month . '-01 00:00:00', 'UTC');
  1188. $max = $date->format('t');
  1189. }
  1190. // If there is no month and year given, default to 31.
  1191. if (empty($max)) {
  1192. $max = 31;
  1193. }
  1194. $none = array(0 => '');
  1195. return !$required ? $none + drupal_map_assoc(range(1, $max)) : drupal_map_assoc(range(1, $max));
  1196. }
  1197. /**
  1198. * Constructs an array of hours.
  1199. *
  1200. * @param string $format
  1201. * A date format string.
  1202. * @param bool $required
  1203. * (optional) If FALSE, the returned array will include a blank value.
  1204. * Defaults to FALSE.
  1205. *
  1206. * @return array
  1207. * An array of hours in the selected format.
  1208. */
  1209. function date_hours($format = 'H', $required = FALSE) {
  1210. $hours = array();
  1211. if ($format == 'h' || $format == 'g') {
  1212. $min = 1;
  1213. $max = 12;
  1214. }
  1215. else {
  1216. $min = 0;
  1217. $max = 23;
  1218. }
  1219. for ($i = $min; $i <= $max; $i++) {
  1220. $hours[$i] = $i < 10 && ($format == 'H' || $format == 'h') ? "0$i" : $i;
  1221. }
  1222. $none = array('' => '');
  1223. return !$required ? $none + $hours : $hours;
  1224. }
  1225. /**
  1226. * Constructs an array of minutes.
  1227. *
  1228. * @param string $format
  1229. * A date format string.
  1230. * @param bool $required
  1231. * (optional) If FALSE, the returned array will include a blank value.
  1232. * Defaults to FALSE.
  1233. *
  1234. * @return array
  1235. * An array of minutes in the selected format.
  1236. */
  1237. function date_minutes($format = 'i', $required = FALSE, $increment = 1) {
  1238. $minutes = array();
  1239. // Ensure $increment has a value so we don't loop endlessly.
  1240. if (empty($increment)) {
  1241. $increment = 1;
  1242. }
  1243. for ($i = 0; $i < 60; $i += $increment) {
  1244. $minutes[$i] = $i < 10 && $format == 'i' ? "0$i" : $i;
  1245. }
  1246. $none = array('' => '');
  1247. return !$required ? $none + $minutes : $minutes;
  1248. }
  1249. /**
  1250. * Constructs an array of seconds.
  1251. *
  1252. * @param string $format
  1253. * A date format string.
  1254. * @param bool $required
  1255. * (optional) If FALSE, the returned array will include a blank value.
  1256. * Defaults to FALSE.
  1257. *
  1258. * @return array
  1259. * An array of seconds in the selected format.
  1260. */
  1261. function date_seconds($format = 's', $required = FALSE, $increment = 1) {
  1262. $seconds = array();
  1263. // Ensure $increment has a value so we don't loop endlessly.
  1264. if (empty($increment)) {
  1265. $increment = 1;
  1266. }
  1267. for ($i = 0; $i < 60; $i += $increment) {
  1268. $seconds[$i] = $i < 10 && $format == 's' ? "0$i" : $i;
  1269. }
  1270. $none = array('' => '');
  1271. return !$required ? $none + $seconds : $seconds;
  1272. }
  1273. /**
  1274. * Constructs an array of AM and PM options.
  1275. *
  1276. * @param bool $required
  1277. * (optional) If FALSE, the returned array will include a blank value.
  1278. * Defaults to FALSE.
  1279. *
  1280. * @return array
  1281. * An array of AM and PM options.
  1282. */
  1283. function date_ampm($required = FALSE) {
  1284. $none = array('' => '');
  1285. $ampm = array(
  1286. 'am' => t('am', array(), array('context' => 'ampm')),
  1287. 'pm' => t('pm', array(), array('context' => 'ampm')),
  1288. );
  1289. return !$required ? $none + $ampm : $ampm;
  1290. }
  1291. /**
  1292. * Constructs an array of regex replacement strings for date format elements.
  1293. *
  1294. * @param bool $strict
  1295. * Whether or not to force 2 digits for elements that sometimes allow either
  1296. * 1 or 2 digits.
  1297. *
  1298. * @return array
  1299. * An array of date() format letters and their regex equivalents.
  1300. */
  1301. function date_format_patterns($strict = FALSE) {
  1302. return array(
  1303. 'd' => '\d{' . ($strict ? '2' : '1,2') . '}',
  1304. 'm' => '\d{' . ($strict ? '2' : '1,2') . '}',
  1305. 'h' => '\d{' . ($strict ? '2' : '1,2') . '}',
  1306. 'H' => '\d{' . ($strict ? '2' : '1,2') . '}',
  1307. 'i' => '\d{' . ($strict ? '2' : '1,2') . '}',
  1308. 's' => '\d{' . ($strict ? '2' : '1,2') . '}',
  1309. 'j' => '\d{1,2}',
  1310. 'N' => '\d',
  1311. 'S' => '\w{2}',
  1312. 'w' => '\d',
  1313. 'z' => '\d{1,3}',
  1314. 'W' => '\d{1,2}',
  1315. 'n' => '\d{1,2}',
  1316. 't' => '\d{2}',
  1317. 'L' => '\d',
  1318. 'o' => '\d{4}',
  1319. 'Y' => '-?\d{1,6}',
  1320. 'y' => '\d{2}',
  1321. 'B' => '\d{3}',
  1322. 'g' => '\d{1,2}',
  1323. 'G' => '\d{1,2}',
  1324. 'e' => '\w*',
  1325. 'I' => '\d',
  1326. 'T' => '\w*',
  1327. 'U' => '\d*',
  1328. 'z' => '[+-]?\d*',
  1329. 'O' => '[+-]?\d{4}',
  1330. // Using S instead of w and 3 as well as 4 to pick up non-ASCII chars like
  1331. // German umlaut. Per http://drupal.org/node/1101284, we may need as little
  1332. // as 2 and as many as 5 characters in some languages.
  1333. 'D' => '\S{2,5}',
  1334. 'l' => '\S*',
  1335. 'M' => '\S{2,5}',
  1336. 'F' => '\S*',
  1337. 'P' => '[+-]?\d{2}\:\d{2}',
  1338. 'O' => '[+-]\d{4}',
  1339. 'c' => '(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-]?\d{2}\:\d{2})',
  1340. 'r' => '(\w{3}), (\d{2})\s(\w{3})\s(\d{2,4})\s(\d{2}):(\d{2}):(\d{2})([+-]?\d{4})?',
  1341. );
  1342. }
  1343. /**
  1344. * Constructs an array of granularity options and their labels.
  1345. *
  1346. * @return array
  1347. * An array of translated date parts, keyed by their machine name.
  1348. */
  1349. function date_granularity_names() {
  1350. return array(
  1351. 'year' => t('Year', array(), array('context' => 'datetime')),
  1352. 'month' => t('Month', array(), array('context' => 'datetime')),
  1353. 'day' => t('Day', array(), array('context' => 'datetime')),
  1354. 'hour' => t('Hour', array(), array('context' => 'datetime')),
  1355. 'minute' => t('Minute', array(), array('context' => 'datetime')),
  1356. 'second' => t('Second', array(), array('context' => 'datetime')),
  1357. );
  1358. }
  1359. /**
  1360. * Sorts a granularity array.
  1361. *
  1362. * @param array $granularity
  1363. * An array of date parts.
  1364. */
  1365. function date_granularity_sorted($granularity) {
  1366. return array_intersect(array('year', 'month', 'day', 'hour', 'minute', 'second'), $granularity);
  1367. }
  1368. /**
  1369. * Constructs an array of granularity based on a given precision.
  1370. *
  1371. * @param string $precision
  1372. * A granularity item.
  1373. *
  1374. * @return array
  1375. * A granularity array containing the given precision and all those above it.
  1376. * For example, passing in 'month' will return array('year', 'month').
  1377. */
  1378. function date_granularity_array_from_precision($precision) {
  1379. $granularity_array = array('year', 'month', 'day', 'hour', 'minute', 'second');
  1380. switch ($precision) {
  1381. case 'year':
  1382. return array_slice($granularity_array, -6, 1);
  1383. case 'month':
  1384. return array_slice($granularity_array, -6, 2);
  1385. case 'day':
  1386. return array_slice($granularity_array, -6, 3);
  1387. case 'hour':
  1388. return array_slice($granularity_array, -6, 4);
  1389. case 'minute':
  1390. return array_slice($granularity_array, -6, 5);
  1391. default:
  1392. return $granularity_array;
  1393. }
  1394. }
  1395. /**
  1396. * Give a granularity array, return the highest precision.
  1397. *
  1398. * @param array $granularity_array
  1399. * An array of date parts.
  1400. *
  1401. * @return string
  1402. * The most precise element in a granularity array.
  1403. */
  1404. function date_granularity_precision($granularity_array) {
  1405. $input = date_granularity_sorted($granularity_array);
  1406. return array_pop($input);
  1407. }
  1408. /**
  1409. * Constructs a valid DATETIME format string for the granularity of an item.
  1410. *
  1411. * @todo This function is no longer used as of
  1412. * http://drupalcode.org/project/date.git/commit/07efbb5.
  1413. */
  1414. function date_granularity_format($granularity) {
  1415. if (is_array($granularity)) {
  1416. $granularity = date_granularity_precision($granularity);
  1417. }
  1418. $format = 'Y-m-d H:i:s';
  1419. switch ($granularity) {
  1420. case 'year':
  1421. return substr($format, 0, 1);
  1422. case 'month':
  1423. return substr($format, 0, 3);
  1424. case 'day':
  1425. return substr($format, 0, 5);
  1426. case 'hour';
  1427. return substr($format, 0, 7);
  1428. case 'minute':
  1429. return substr($format, 0, 9);
  1430. default:
  1431. return $format;
  1432. }
  1433. }
  1434. /**
  1435. * Returns a translated array of timezone names.
  1436. *
  1437. * Cache the untranslated array, make the translated array a static variable.
  1438. *
  1439. * @param bool $required
  1440. * (optional) If FALSE, the returned array will include a blank value.
  1441. * Defaults to FALSE.
  1442. * @param bool $refresh
  1443. * (optional) Whether to refresh the list. Defaults to TRUE.
  1444. *
  1445. * @return array
  1446. * An array of timezone names.
  1447. */
  1448. function date_timezone_names($required = FALSE, $refresh = FALSE) {
  1449. static $zonenames;
  1450. if (empty($zonenames) || $refresh) {
  1451. $cached = cache_get('date_timezone_identifiers_list');
  1452. $zonenames = !empty($cached) ? $cached->data : array();
  1453. if ($refresh || empty($cached) || empty($zonenames)) {
  1454. $data = timezone_identifiers_list();
  1455. asort($data);
  1456. foreach ($data as $delta => $zone) {
  1457. // Because many timezones exist in PHP only for backward compatibility
  1458. // reasons and should not be used, the list is filtered by a regular
  1459. // expression.
  1460. if (preg_match('!^((Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/|UTC$)!', $zone)) {
  1461. $zonenames[$zone] = $zone;
  1462. }
  1463. }
  1464. if (!empty($zonenames)) {
  1465. cache_set('date_timezone_identifiers_list', $zonenames);
  1466. }
  1467. }
  1468. foreach ($zonenames as $zone) {
  1469. $zonenames[$zone] = t('!timezone', array('!timezone' => t($zone)));
  1470. }
  1471. }
  1472. $none = array('' => '');
  1473. return !$required ? $none + $zonenames : $zonenames;
  1474. }
  1475. /**
  1476. * Returns an array of system-allowed timezone abbreviations.
  1477. *
  1478. * Cache an array of just the abbreviation names because the whole
  1479. * timezone_abbreviations_list() is huge, so we don't want to retrieve it more
  1480. * than necessary.
  1481. *
  1482. * @param bool $refresh
  1483. * (optional) Whether to refresh the list. Defaults to TRUE.
  1484. *
  1485. * @return array
  1486. * An array of allowed timezone abbreviations.
  1487. */
  1488. function date_timezone_abbr($refresh = FALSE) {
  1489. $cached = cache_get('date_timezone_abbreviations');
  1490. $data = isset($cached->data) ? $cached->data : array();
  1491. if (empty($data) || $refresh) {
  1492. $data = array_keys(timezone_abbreviations_list());
  1493. cache_set('date_timezone_abbreviations', $data);
  1494. }
  1495. return $data;
  1496. }
  1497. /**
  1498. * Formats a date, using a date type or a custom date format string.
  1499. *
  1500. * Reworked from Drupal's format_date function to handle pre-1970 and
  1501. * post-2038 dates and accept a date object instead of a timestamp as input.
  1502. * Translates formatted date results, unlike PHP function date_format().
  1503. * Should only be used for display, not input, because it can't be parsed.
  1504. *
  1505. * @param object $date
  1506. * A date object.
  1507. * @param string $type
  1508. * (optional) The date format to use. Can be 'small', 'medium' or 'large' for
  1509. * the preconfigured date formats. If 'custom' is specified, then $format is
  1510. * required as well. Defaults to 'medium'.
  1511. * @param string $format
  1512. * (optional) A PHP date format string as required by date(). A backslash
  1513. * should be used before a character to avoid interpreting the character as
  1514. * part of a date format. Defaults to an empty string.
  1515. * @param string $langcode
  1516. * (optional) Language code to translate to. Defaults to NULL.
  1517. *
  1518. * @return string
  1519. * A translated date string in the requested format.
  1520. *
  1521. * @see format_date()
  1522. */
  1523. function date_format_date($date, $type = 'medium', $format = '', $langcode = NULL) {
  1524. if (empty($date)) {
  1525. return '';
  1526. }
  1527. if ($type != 'custom') {
  1528. $format = variable_get('date_format_' . $type);
  1529. }
  1530. if ($type != 'custom' && empty($format)) {
  1531. $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
  1532. }
  1533. $format = date_limit_format($format, $date->granularity);
  1534. $max = strlen($format);
  1535. $datestring = '';
  1536. for ($i = 0; $i < $max; $i++) {
  1537. $c = $format[$i];
  1538. switch ($c) {
  1539. case 'l':
  1540. $datestring .= t($date->format('l'), array(), array('context' => '', 'langcode' => $langcode));
  1541. break;
  1542. case 'D':
  1543. $datestring .= t($date->format('D'), array(), array('context' => '', 'langcode' => $langcode));
  1544. break;
  1545. case 'F':
  1546. $datestring .= t($date->format('F'), array(), array('context' => 'Long month name', 'langcode' => $langcode));
  1547. break;
  1548. case 'M':
  1549. $datestring .= t($date->format('M'), array(), array('langcode' => $langcode));
  1550. break;
  1551. case 'A':
  1552. case 'a':
  1553. $datestring .= t($date->format($c), array(), array('context' => 'ampm', 'langcode' => $langcode));
  1554. break;
  1555. // The timezone name translations can use t().
  1556. case 'e':
  1557. case 'T':
  1558. $datestring .= t($date->format($c));
  1559. break;
  1560. // Remaining date parts need no translation.
  1561. case 'O':
  1562. $datestring .= sprintf('%s%02d%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60);
  1563. break;
  1564. case 'P':
  1565. $datestring .= sprintf('%s%02d:%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60);
  1566. break;
  1567. case 'Z':
  1568. $datestring .= date_offset_get($date);
  1569. break;
  1570. case '\\':
  1571. $datestring .= $format[++$i];
  1572. break;
  1573. case 'r':
  1574. $datestring .= date_format_date($date, 'custom', 'D, d M Y H:i:s O', $langcode);
  1575. break;
  1576. default:
  1577. if (strpos('BdcgGhHiIjLmnNosStTuUwWYyz', $c) !== FALSE) {
  1578. $datestring .= $date->format($c);
  1579. }
  1580. else {
  1581. $datestring .= $c;
  1582. }
  1583. }
  1584. }
  1585. return $datestring;
  1586. }
  1587. /**
  1588. * Formats a time interval with granularity, including past and future context.
  1589. *
  1590. * @param object $date
  1591. * The current date object.
  1592. * @param int $granularity
  1593. * (optional) Number of units to display in the string. Defaults to 2.
  1594. *
  1595. * @return string
  1596. * A translated string representation of the interval.
  1597. *
  1598. * @see format_interval()
  1599. */
  1600. function date_format_interval($date, $granularity = 2, $display_ago = TRUE) {
  1601. // If no date is sent, then return nothing.
  1602. if (empty($date)) {
  1603. return NULL;
  1604. }
  1605. $interval = REQUEST_TIME - $date->format('U');
  1606. if ($interval > 0) {
  1607. return $display_ago ? t('!time ago', array('!time' => format_interval($interval, $granularity))) :
  1608. t('!time', array('!time' => format_interval($interval, $granularity)));
  1609. }
  1610. else {
  1611. return format_interval(abs($interval), $granularity);
  1612. }
  1613. }
  1614. /**
  1615. * A date object for the current time.
  1616. *
  1617. * @param object $timezone
  1618. * (optional) Optionally force time to a specific timezone, defaults to user
  1619. * timezone, if set, otherwise site timezone. Defaults to NULL.
  1620. *
  1621. * @return object
  1622. * The current time as a date object.
  1623. */
  1624. function date_now($timezone = NULL) {
  1625. return new DateObject('now', $timezone);
  1626. }
  1627. /**
  1628. * Determines if a timezone string is valid.
  1629. *
  1630. * @param string $timezone
  1631. * A potentially invalid timezone string.
  1632. *
  1633. * @return bool
  1634. * TRUE if the timezone is valid, FALSE otherwise.
  1635. */
  1636. function date_timezone_is_valid($timezone) {
  1637. static $timezone_names;
  1638. if (empty($timezone_names)) {
  1639. $timezone_names = array_keys(date_timezone_names(TRUE));
  1640. }
  1641. return in_array($timezone, $timezone_names);
  1642. }
  1643. /**
  1644. * Returns a timezone name to use as a default.
  1645. *
  1646. * @param bool $check_user
  1647. * (optional) Whether or not to check for a user-configured timezone.
  1648. * Defaults to TRUE.
  1649. *
  1650. * @return string
  1651. * The default timezone for a user, if available, otherwise the site.
  1652. */
  1653. function date_default_timezone($check_user = TRUE) {
  1654. global $user;
  1655. if ($check_user && variable_get('configurable_timezones', 1) && !empty($user->timezone)) {
  1656. return $user->timezone;
  1657. }
  1658. else {
  1659. $default = variable_get('date_default_timezone', '');
  1660. return empty($default) ? 'UTC' : $default;
  1661. }
  1662. }
  1663. /**
  1664. * Returns a timezone object for the default timezone.
  1665. *
  1666. * @param bool $check_user
  1667. * (optional) Whether or not to check for a user-configured timezone.
  1668. * Defaults to TRUE.
  1669. *
  1670. * @return object
  1671. * The default timezone for a user, if available, otherwise the site.
  1672. */
  1673. function date_default_timezone_object($check_user = TRUE) {
  1674. return timezone_open(date_default_timezone($check_user));
  1675. }
  1676. /**
  1677. * Identifies the number of days in a month for a date.
  1678. */
  1679. function date_days_in_month($year, $month) {
  1680. // Pick a day in the middle of the month to avoid timezone shifts.
  1681. $datetime = date_pad($year, 4) . '-' . date_pad($month) . '-15 00:00:00';
  1682. $date = new DateObject($datetime);
  1683. return $date->format('t');
  1684. }
  1685. /**
  1686. * Identifies the number of days in a year for a date.
  1687. *
  1688. * @param mixed $date
  1689. * (optional) The current date object, or a date string. Defaults to NULL.
  1690. *
  1691. * @return integer
  1692. * The number of days in the year.
  1693. */
  1694. function date_days_in_year($date = NULL) {
  1695. if (empty($date)) {
  1696. $date = date_now();
  1697. }
  1698. elseif (!is_object($date)) {
  1699. $date = new DateObject($date);
  1700. }
  1701. if (is_object($date)) {
  1702. if ($date->format('L')) {
  1703. return 366;
  1704. }
  1705. else {
  1706. return 365;
  1707. }
  1708. }
  1709. return NULL;
  1710. }
  1711. /**
  1712. * Identifies the number of ISO weeks in a year for a date.
  1713. *
  1714. * December 28 is always in the last ISO week of the year.
  1715. *
  1716. * @param mixed $date
  1717. * (optional) The current date object, or a date string. Defaults to NULL.
  1718. *
  1719. * @return integer
  1720. * The number of ISO weeks in a year.
  1721. */
  1722. function date_iso_weeks_in_year($date = NULL) {
  1723. if (empty($date)) {
  1724. $date = date_now();
  1725. }
  1726. elseif (!is_object($date)) {
  1727. $date = new DateObject($date);
  1728. }
  1729. if (is_object($date)) {
  1730. date_date_set($date, $date->format('Y'), 12, 28);
  1731. return $date->format('W');
  1732. }
  1733. return NULL;
  1734. }
  1735. /**
  1736. * Returns day of week for a given date (0 = Sunday).
  1737. *
  1738. * @param mixed $date
  1739. * (optional) A date, default is current local day. Defaults to NULL.
  1740. *
  1741. * @return int
  1742. * The number of the day in the week.
  1743. */
  1744. function date_day_of_week($date = NULL) {
  1745. if (empty($date)) {
  1746. $date = date_now();
  1747. }
  1748. elseif (!is_object($date)) {
  1749. $date = new DateObject($date);
  1750. }
  1751. if (is_object($date)) {
  1752. return $date->format('w');
  1753. }
  1754. return NULL;
  1755. }
  1756. /**
  1757. * Returns translated name of the day of week for a given date.
  1758. *
  1759. * @param mixed $date
  1760. * (optional) A date, default is current local day. Defaults to NULL.
  1761. * @param string $abbr
  1762. * (optional) Whether to return the abbreviated name for that day.
  1763. * Defaults to TRUE.
  1764. *
  1765. * @return string
  1766. * The name of the day in the week for that date.
  1767. */
  1768. function date_day_of_week_name($date = NULL, $abbr = TRUE) {
  1769. if (!is_object($date)) {
  1770. $date = new DateObject($date);
  1771. }
  1772. $dow = date_day_of_week($date);
  1773. $days = $abbr ? date_week_days_abbr() : date_week_days();
  1774. return $days[$dow];
  1775. }
  1776. /**
  1777. * Calculates the start and end dates for a calendar week.
  1778. *
  1779. * The dates are adjusted to use the chosen first day of week for this site.
  1780. *
  1781. * @param int $week
  1782. * The week value.
  1783. * @param int $year
  1784. * The year value.
  1785. *
  1786. * @return array
  1787. * A numeric array containing the start and end dates of a week.
  1788. */
  1789. function date_week_range($week, $year) {
  1790. if (variable_get('date_api_use_iso8601', FALSE)) {
  1791. return date_iso_week_range($week, $year);
  1792. }
  1793. $min_date = new DateObject($year . '-01-01 00:00:00');
  1794. $min_date->setTimezone(date_default_timezone_object());
  1795. // Move to the right week.
  1796. date_modify($min_date, '+' . strval(7 * ($week - 1)) . ' days');
  1797. // Move backwards to the first day of the week.
  1798. $first_day = variable_get('date_first_day', 0);
  1799. $day_wday = date_format($min_date, 'w');
  1800. date_modify($min_date, '-' . strval((7 + $day_wday - $first_day) % 7) . ' days');
  1801. // Move forwards to the last day of the week.
  1802. $max_date = clone($min_date);
  1803. date_modify($max_date, '+7 days');
  1804. if (date_format($min_date, 'Y') != $year) {
  1805. $min_date = new DateObject($year . '-01-01 00:00:00');
  1806. }
  1807. return array($min_date, $max_date);
  1808. }
  1809. /**
  1810. * Calculates the start and end dates for an ISO week.
  1811. *
  1812. * @param int $week
  1813. * The week value.
  1814. * @param int $year
  1815. * The year value.
  1816. *
  1817. * @return array
  1818. * A numeric array containing the start and end dates of an ISO week.
  1819. */
  1820. function date_iso_week_range($week, $year) {
  1821. // Get to the last ISO week of the previous year.
  1822. $min_date = new DateObject(($year - 1) . '-12-28 00:00:00');
  1823. date_timezone_set($min_date, date_default_timezone_object());
  1824. // Find the first day of the first ISO week in the year.
  1825. date_modify($min_date, '+1 Monday');
  1826. // Jump ahead to the desired week for the beginning of the week range.
  1827. if ($week > 1) {
  1828. date_modify($min_date, '+ ' . ($week - 1) . ' weeks');
  1829. }
  1830. // Move forwards to the last day of the week.
  1831. $max_date = clone($min_date);
  1832. date_modify($max_date, '+7 days');
  1833. return array($min_date, $max_date);
  1834. }
  1835. /**
  1836. * The number of calendar weeks in a year.
  1837. *
  1838. * PHP week functions return the ISO week, not the calendar week.
  1839. *
  1840. * @param int $year
  1841. * A year value.
  1842. *
  1843. * @return int
  1844. * Number of calendar weeks in selected year.
  1845. */
  1846. function date_weeks_in_year($year) {
  1847. $date = new DateObject(($year + 1) . '-01-01 12:00:00', 'UTC');
  1848. date_modify($date, '-1 day');
  1849. return date_week($date->format('Y-m-d'));
  1850. }
  1851. /**
  1852. * The calendar week number for a date.
  1853. *
  1854. * PHP week functions return the ISO week, not the calendar week.
  1855. *
  1856. * @param string $date
  1857. * A date string in the format Y-m-d.
  1858. *
  1859. * @return int
  1860. * The calendar week number.
  1861. */
  1862. function date_week($date) {
  1863. $date = substr($date, 0, 10);
  1864. $parts = explode('-', $date);
  1865. $date = new DateObject($date . ' 12:00:00', 'UTC');
  1866. // If we are using ISO weeks, this is easy.
  1867. if (variable_get('date_api_use_iso8601', FALSE)) {
  1868. return intval($date->format('W'));
  1869. }
  1870. $year_date = new DateObject($parts[0] . '-01-01 12:00:00', 'UTC');
  1871. $week = intval($date->format('W'));
  1872. $year_week = intval(date_format($year_date, 'W'));
  1873. $date_year = intval($date->format('o'));
  1874. // Remove the leap week if it's present.
  1875. if ($date_year > intval($parts[0])) {
  1876. $last_date = clone($date);
  1877. date_modify($last_date, '-7 days');
  1878. $week = date_format($last_date, 'W') + 1;
  1879. }
  1880. elseif ($date_year < intval($parts[0])) {
  1881. $week = 0;
  1882. }
  1883. if ($year_week != 1) {
  1884. $week++;
  1885. }
  1886. // Convert to ISO-8601 day number, to match weeks calculated above.
  1887. $iso_first_day = 1 + (variable_get('date_first_day', 0) + 6) % 7;
  1888. // If it's before the starting day, it's the previous week.
  1889. if (intval($date->format('N')) < $iso_first_day) {
  1890. $week--;
  1891. }
  1892. // If the year starts before, it's an extra week at the beginning.
  1893. if (intval(date_format($year_date, 'N')) < $iso_first_day) {
  1894. $week++;
  1895. }
  1896. return $week;
  1897. }
  1898. /**
  1899. * Helper function to left pad date parts with zeros.
  1900. *
  1901. * Provided because this is needed so often with dates.
  1902. *
  1903. * @param int $value
  1904. * The value to pad.
  1905. * @param int $size
  1906. * (optional) Total size expected, usually 2 or 4. Defaults to 2.
  1907. *
  1908. * @return string
  1909. * The padded value.
  1910. */
  1911. function date_pad($value, $size = 2) {
  1912. return sprintf("%0" . $size . "d", $value);
  1913. }
  1914. /**
  1915. * Determines if the granularity contains a time portion.
  1916. *
  1917. * @param array $granularity
  1918. * An array of allowed date parts, all others will be removed.
  1919. *
  1920. * @return bool
  1921. * TRUE if the granularity contains a time portion, FALSE otherwise.
  1922. */
  1923. function date_has_time($granularity) {
  1924. if (!is_array($granularity)) {
  1925. $granularity = array();
  1926. }
  1927. return (bool) count(array_intersect($granularity, array('hour', 'minute', 'second')));
  1928. }
  1929. /**
  1930. * Determines if the granularity contains a date portion.
  1931. *
  1932. * @param array $granularity
  1933. * An array of allowed date parts, all others will be removed.
  1934. *
  1935. * @return bool
  1936. * TRUE if the granularity contains a date portion, FALSE otherwise.
  1937. */
  1938. function date_has_date($granularity) {
  1939. if (!is_array($granularity)) {
  1940. $granularity = array();
  1941. }
  1942. return (bool) count(array_intersect($granularity, array('year', 'month', 'day')));
  1943. }
  1944. /**
  1945. * Helper function to get a format for a specific part of a date field.
  1946. *
  1947. * @param string $part
  1948. * The date field part, either 'time' or 'date'.
  1949. * @param string $format
  1950. * A date format string.
  1951. *
  1952. * @return string
  1953. * The date format for the given part.
  1954. */
  1955. function date_part_format($part, $format) {
  1956. switch ($part) {
  1957. case 'date':
  1958. return date_limit_format($format, array('year', 'month', 'day'));
  1959. case 'time':
  1960. return date_limit_format($format, array('hour', 'minute', 'second'));
  1961. default:
  1962. return date_limit_format($format, array($part));
  1963. }
  1964. }
  1965. /**
  1966. * Limits a date format to include only elements from a given granularity array.
  1967. *
  1968. * Example:
  1969. * date_limit_format('F j, Y - H:i', array('year', 'month', 'day'));
  1970. * returns 'F j, Y'
  1971. *
  1972. * @param string $format
  1973. * A date format string.
  1974. * @param array $granularity
  1975. * An array of allowed date parts, all others will be removed.
  1976. *
  1977. * @return string
  1978. * The format string with all other elements removed.
  1979. */
  1980. function date_limit_format($format, $granularity) {
  1981. // If punctuation has been escaped, remove the escaping. Done using strtr()
  1982. // because it is easier than getting the escape character extracted using
  1983. // preg_replace().
  1984. $replace = array(
  1985. '\-' => '-',
  1986. '\:' => ':',
  1987. "\'" => "'",
  1988. '\. ' => ' . ',
  1989. '\,' => ',',
  1990. );
  1991. $format = strtr($format, $replace);
  1992. // Get the 'T' out of ISO date formats that don't have both date and time.
  1993. if (!date_has_time($granularity) || !date_has_date($granularity)) {
  1994. $format = str_replace('\T', ' ', $format);
  1995. $format = str_replace('T', ' ', $format);
  1996. }
  1997. $regex = array();
  1998. if (!date_has_time($granularity)) {
  1999. $regex[] = '((?<!\\\\)[a|A])';
  2000. }
  2001. // Create regular expressions to remove selected values from string.
  2002. // Use (?<!\\\\) to keep escaped letters from being removed.
  2003. foreach (date_nongranularity($granularity) as $element) {
  2004. switch ($element) {
  2005. case 'year':
  2006. $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[Yy])';
  2007. break;
  2008. case 'day':
  2009. $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[l|D|d|dS|j|jS|N|w|W|z]{1,2})';
  2010. break;
  2011. case 'month':
  2012. $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[FMmn])';
  2013. break;
  2014. case 'hour':
  2015. $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[HhGg])';
  2016. break;
  2017. case 'minute':
  2018. $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[i])';
  2019. break;
  2020. case 'second':
  2021. $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[s])';
  2022. break;
  2023. case 'timezone':
  2024. $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[TOZPe])';
  2025. break;
  2026. }
  2027. }
  2028. // Remove empty parentheses, brackets, pipes.
  2029. $regex[] = '(\(\))';
  2030. $regex[] = '(\[\])';
  2031. $regex[] = '(\|\|)';
  2032. // Remove selected values from string.
  2033. $format = trim(preg_replace($regex, array(), $format));
  2034. // Remove orphaned punctuation at the beginning of the string.
  2035. $format = preg_replace('`^([\-/\.,:\'])`', '', $format);
  2036. // Remove orphaned punctuation at the end of the string.
  2037. $format = preg_replace('([\-/,:\']$)', '', $format);
  2038. $format = preg_replace('(\\$)', '', $format);
  2039. // Trim any whitespace from the result.
  2040. $format = trim($format);
  2041. // After removing the non-desired parts of the format, test if the only things
  2042. // left are escaped, non-date, characters. If so, return nothing.
  2043. // Using S instead of w to pick up non-ASCII characters.
  2044. $test = trim(preg_replace('(\\\\\S{1,3})', '', $format));
  2045. if (empty($test)) {
  2046. $format = '';
  2047. }
  2048. return $format;
  2049. }
  2050. /**
  2051. * Converts a format to an ordered array of granularity parts.
  2052. *
  2053. * Example:
  2054. * date_format_order('m/d/Y H:i')
  2055. * returns
  2056. * array(
  2057. * 0 => 'month',
  2058. * 1 => 'day',
  2059. * 2 => 'year',
  2060. * 3 => 'hour',
  2061. * 4 => 'minute',
  2062. * );
  2063. *
  2064. * @param string $format
  2065. * A date format string.
  2066. *
  2067. * @return array
  2068. * An array of ordered granularity elements from the given format string.
  2069. */
  2070. function date_format_order($format) {
  2071. $order = array();
  2072. if (empty($format)) {
  2073. return $order;
  2074. }
  2075. $max = strlen($format);
  2076. for ($i = 0; $i <= $max; $i++) {
  2077. if (!isset($format[$i])) {
  2078. break;
  2079. }
  2080. switch ($format[$i]) {
  2081. case 'd':
  2082. case 'j':
  2083. $order[] = 'day';
  2084. break;
  2085. case 'F':
  2086. case 'M':
  2087. case 'm':
  2088. case 'n':
  2089. $order[] = 'month';
  2090. break;
  2091. case 'Y':
  2092. case 'y':
  2093. $order[] = 'year';
  2094. break;
  2095. case 'g':
  2096. case 'G':
  2097. case 'h':
  2098. case 'H':
  2099. $order[] = 'hour';
  2100. break;
  2101. case 'i':
  2102. $order[] = 'minute';
  2103. break;
  2104. case 's':
  2105. $order[] = 'second';
  2106. break;
  2107. }
  2108. }
  2109. return $order;
  2110. }
  2111. /**
  2112. * Strips out unwanted granularity elements.
  2113. *
  2114. * @param array $granularity
  2115. * An array like ('year', 'month', 'day', 'hour', 'minute', 'second');
  2116. *
  2117. * @return array
  2118. * A reduced set of granularitiy elements.
  2119. */
  2120. function date_nongranularity($granularity) {
  2121. return array_diff(array('year', 'month', 'day', 'hour', 'minute', 'second', 'timezone'), (array) $granularity);
  2122. }
  2123. /**
  2124. * Implements hook_element_info().
  2125. */
  2126. function date_api_element_info() {
  2127. module_load_include('inc', 'date_api', 'date_api_elements');
  2128. return _date_api_element_info();
  2129. }
  2130. /**
  2131. * Implements hook_theme().
  2132. */
  2133. function date_api_theme($existing, $type, $theme, $path) {
  2134. $base = array(
  2135. 'file' => 'theme.inc',
  2136. 'path' => "$path/theme",
  2137. );
  2138. return array(
  2139. 'date_nav_title' => $base + array('variables' => array('granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL)),
  2140. 'date_timezone' => $base + array('render element' => 'element'),
  2141. 'date_select' => $base + array('render element' => 'element'),
  2142. 'date_text' => $base + array('render element' => 'element'),
  2143. 'date_select_element' => $base + array('render element' => 'element'),
  2144. 'date_textfield_element' => $base + array('render element' => 'element'),
  2145. 'date_part_hour_prefix' => $base + array('render element' => 'element'),
  2146. 'date_part_minsec_prefix' => $base + array('render element' => 'element'),
  2147. 'date_part_label_year' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
  2148. 'date_part_label_month' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
  2149. 'date_part_label_day' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
  2150. 'date_part_label_hour' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
  2151. 'date_part_label_minute' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
  2152. 'date_part_label_second' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
  2153. 'date_part_label_ampm' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
  2154. 'date_part_label_timezone' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
  2155. 'date_part_label_date' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
  2156. 'date_part_label_time' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
  2157. 'date_views_filter_form' => $base + array('template' => 'date-views-filter-form', 'render element' => 'form'),
  2158. 'date_calendar_day' => $base + array('variables' => array('date' => NULL)),
  2159. 'date_time_ago' => $base + array('variables' => array('start_date' => NULL, 'end_date' => NULL, 'interval' => NULL)),
  2160. );
  2161. }
  2162. /**
  2163. * Function to figure out which local timezone applies to a date and select it.
  2164. *
  2165. * @param string $handling
  2166. * The timezone handling.
  2167. * @param string $timezone
  2168. * (optional) A timezone string. Defaults to an empty string.
  2169. *
  2170. * @return string
  2171. * The timezone string.
  2172. */
  2173. function date_get_timezone($handling, $timezone = '') {
  2174. switch ($handling) {
  2175. case 'date':
  2176. $timezone = !empty($timezone) ? $timezone : date_default_timezone();
  2177. break;
  2178. case 'utc':
  2179. $timezone = 'UTC';
  2180. break;
  2181. default:
  2182. $timezone = date_default_timezone();
  2183. }
  2184. return $timezone > '' ? $timezone : date_default_timezone();
  2185. }
  2186. /**
  2187. * Function to figure out which db timezone applies to a date and select it.
  2188. *
  2189. * @param string $handling
  2190. * The timezone handling.
  2191. * @param string $timezone
  2192. * (optional) A timezone string. Defaults to an empty string.
  2193. *
  2194. * @return string
  2195. * The timezone string.
  2196. */
  2197. function date_get_timezone_db($handling, $timezone = '') {
  2198. switch ($handling) {
  2199. case 'none':
  2200. $timezone = date_default_timezone();
  2201. break;
  2202. default:
  2203. $timezone = 'UTC';
  2204. break;
  2205. }
  2206. return $timezone > '' ? $timezone : 'UTC';
  2207. }
  2208. /**
  2209. * Helper function for converting back and forth from '+1' to 'First'.
  2210. */
  2211. function date_order_translated() {
  2212. return array(
  2213. '+1' => t('First', array(), array('context' => 'date_order')),
  2214. '+2' => t('Second', array(), array('context' => 'date_order')),
  2215. '+3' => t('Third', array(), array('context' => 'date_order')),
  2216. '+4' => t('Fourth', array(), array('context' => 'date_order')),
  2217. '+5' => t('Fifth', array(), array('context' => 'date_order')),
  2218. '-1' => t('Last', array(), array('context' => 'date_order_reverse')),
  2219. '-2' => t('Next to last', array(), array('context' => 'date_order_reverse')),
  2220. '-3' => t('Third from last', array(), array('context' => 'date_order_reverse')),
  2221. '-4' => t('Fourth from last', array(), array('context' => 'date_order_reverse')),
  2222. '-5' => t('Fifth from last', array(), array('context' => 'date_order_reverse')),
  2223. );
  2224. }
  2225. /**
  2226. * Creates an array of ordered strings, using English text when possible.
  2227. */
  2228. function date_order() {
  2229. return array(
  2230. '+1' => 'First',
  2231. '+2' => 'Second',
  2232. '+3' => 'Third',
  2233. '+4' => 'Fourth',
  2234. '+5' => 'Fifth',
  2235. '-1' => 'Last',
  2236. '-2' => '-2',
  2237. '-3' => '-3',
  2238. '-4' => '-4',
  2239. '-5' => '-5',
  2240. );
  2241. }
  2242. /**
  2243. * Tests validity of a date range string.
  2244. *
  2245. * @param string $string
  2246. * A min and max year string like '-3:+1'a.
  2247. *
  2248. * @return bool
  2249. * TRUE if the date range is valid, FALSE otherwise.
  2250. */
  2251. function date_range_valid($string) {
  2252. $matches = preg_match('@^(\-[0-9]+|[0-9]{4}):([\+|\-][0-9]+|[0-9]{4})$@', $string);
  2253. return $matches < 1 ? FALSE : TRUE;
  2254. }
  2255. /**
  2256. * Splits a string like -3:+3 or 2001:2010 into an array of min and max years.
  2257. *
  2258. * Center the range around the current year, if any, but expand it far
  2259. * enough so it will pick up the year value in the field in case
  2260. * the value in the field is outside the initial range.
  2261. *
  2262. * @param string $string
  2263. * A min and max year string like '-3:+1'.
  2264. * @param object $date
  2265. * (optional) A date object. Defaults to NULL.
  2266. *
  2267. * @return array
  2268. * A numerically indexed array, containing a minimum and maximum year.
  2269. */
  2270. function date_range_years($string, $date = NULL) {
  2271. $this_year = date_format(date_now(), 'Y');
  2272. list($min_year, $max_year) = explode(':', $string);
  2273. // Valid patterns would be -5:+5, 0:+1, 2008:2010.
  2274. $plus_pattern = '@[\+|\-][0-9]{1,4}@';
  2275. $year_pattern = '@^[0-9]{4}@';
  2276. if (!preg_match($year_pattern, $min_year, $matches)) {
  2277. if (preg_match($plus_pattern, $min_year, $matches)) {
  2278. $min_year = $this_year + $matches[0];
  2279. }
  2280. else {
  2281. $min_year = $this_year;
  2282. }
  2283. }
  2284. if (!preg_match($year_pattern, $max_year, $matches)) {
  2285. if (preg_match($plus_pattern, $max_year, $matches)) {
  2286. $max_year = $this_year + $matches[0];
  2287. }
  2288. else {
  2289. $max_year = $this_year;
  2290. }
  2291. }
  2292. // We expect the $min year to be less than the $max year.
  2293. // Some custom values for -99:+99 might not obey that.
  2294. if ($min_year > $max_year) {
  2295. $temp = $max_year;
  2296. $max_year = $min_year;
  2297. $min_year = $temp;
  2298. }
  2299. // If there is a current value, stretch the range to include it.
  2300. $value_year = is_object($date) ? $date->format('Y') : '';
  2301. if (!empty($value_year)) {
  2302. $min_year = min($value_year, $min_year);
  2303. $max_year = max($value_year, $max_year);
  2304. }
  2305. return array($min_year, $max_year);
  2306. }
  2307. /**
  2308. * Converts a min and max year into a string like '-3:+1'.
  2309. *
  2310. * @param array $years
  2311. * A numerically indexed array, containing a minimum and maximum year.
  2312. *
  2313. * @return string
  2314. * A min and max year string like '-3:+1'.
  2315. */
  2316. function date_range_string($years) {
  2317. $this_year = date_format(date_now(), 'Y');
  2318. if ($years[0] < $this_year) {
  2319. $min = '-' . ($this_year - $years[0]);
  2320. }
  2321. else {
  2322. $min = '+' . ($years[0] - $this_year);
  2323. }
  2324. if ($years[1] < $this_year) {
  2325. $max = '-' . ($this_year - $years[1]);
  2326. }
  2327. else {
  2328. $max = '+' . ($years[1] - $this_year);
  2329. }
  2330. return $min . ':' . $max;
  2331. }
  2332. /**
  2333. * Temporary helper to re-create equivalent of content_database_info().
  2334. */
  2335. function date_api_database_info($field, $revision = FIELD_LOAD_CURRENT) {
  2336. return array(
  2337. 'columns' => $field['storage']['details']['sql'][$revision],
  2338. 'table' => _field_sql_storage_tablename($field),
  2339. );
  2340. }
  2341. /**
  2342. * Implements hook_form_FORM_ID_alter() for system_regional_settings().
  2343. *
  2344. * Add a form element to configure whether or not week numbers are ISO-8601, the
  2345. * default is FALSE (US/UK/AUS norm).
  2346. */
  2347. function date_api_form_system_regional_settings_alter(&$form, &$form_state, $form_id) {
  2348. $form['locale']['date_api_use_iso8601'] = array(
  2349. '#type' => 'checkbox',
  2350. '#title' => t('Use ISO-8601 week numbers'),
  2351. '#default_value' => variable_get('date_api_use_iso8601', FALSE),
  2352. '#description' => t('IMPORTANT! If checked, First day of week MUST be set to Monday'),
  2353. );
  2354. $form['#validate'][] = 'date_api_form_system_settings_validate';
  2355. }
  2356. /**
  2357. * Validate that the option to use ISO weeks matches first day of week choice.
  2358. */
  2359. function date_api_form_system_settings_validate(&$form, &$form_state) {
  2360. $form_values = $form_state['values'];
  2361. if ($form_values['date_api_use_iso8601'] && $form_values['date_first_day'] != 1) {
  2362. form_set_error('date_first_day', t('When using ISO-8601 week numbers, the first day of the week must be set to Monday.'));
  2363. }
  2364. }
  2365. /**
  2366. * Creates an array of date format types for use as an options list.
  2367. */
  2368. function date_format_type_options() {
  2369. $options = array();
  2370. $format_types = system_get_date_types();
  2371. if (!empty($format_types)) {
  2372. foreach ($format_types as $type => $type_info) {
  2373. $options[$type] = $type_info['title'] . ' (' . date_format_date(date_example_date(), $type) . ')';
  2374. }
  2375. }
  2376. return $options;
  2377. }
  2378. /**
  2379. * Creates an example date.
  2380. *
  2381. * This ensures a clear difference between month and day, and 12 and 24 hours.
  2382. */
  2383. function date_example_date() {
  2384. $now = date_now();
  2385. if (date_format($now, 'M') == date_format($now, 'F')) {
  2386. date_modify($now, '+1 month');
  2387. }
  2388. if (date_format($now, 'm') == date_format($now, 'd')) {
  2389. date_modify($now, '+1 day');
  2390. }
  2391. if (date_format($now, 'H') == date_format($now, 'h')) {
  2392. date_modify($now, '+12 hours');
  2393. }
  2394. return $now;
  2395. }
  2396. /**
  2397. * Determine if a start/end date combination qualify as 'All day'.
  2398. *
  2399. * @param string $string1
  2400. * A string date in datetime format for the 'start' date.
  2401. * @param string $string2
  2402. * A string date in datetime format for the 'end' date.
  2403. * @param string $granularity
  2404. * (optional) The granularity of the date. Defaults to 'second'.
  2405. * @param int $increment
  2406. * (optional) The increment of the date. Defaults to 1.
  2407. *
  2408. * @return bool
  2409. * TRUE if the date is all day, FALSE otherwise.
  2410. */
  2411. function date_is_all_day($string1, $string2, $granularity = 'second', $increment = 1) {
  2412. if (empty($string1) || empty($string2)) {
  2413. return FALSE;
  2414. }
  2415. elseif (!in_array($granularity, array('hour', 'minute', 'second'))) {
  2416. return FALSE;
  2417. }
  2418. preg_match('/([0-9]{4}-[0-9]{2}-[0-9]{2}) (([0-9]{2}):([0-9]{2}):([0-9]{2}))/', $string1, $matches);
  2419. $count = count($matches);
  2420. $date1 = $count > 1 ? $matches[1] : '';
  2421. $time1 = $count > 2 ? $matches[2] : '';
  2422. $hour1 = $count > 3 ? intval($matches[3]) : 0;
  2423. $min1 = $count > 4 ? intval($matches[4]) : 0;
  2424. $sec1 = $count > 5 ? intval($matches[5]) : 0;
  2425. preg_match('/([0-9]{4}-[0-9]{2}-[0-9]{2}) (([0-9]{2}):([0-9]{2}):([0-9]{2}))/', $string2, $matches);
  2426. $count = count($matches);
  2427. $date2 = $count > 1 ? $matches[1] : '';
  2428. $time2 = $count > 2 ? $matches[2] : '';
  2429. $hour2 = $count > 3 ? intval($matches[3]) : 0;
  2430. $min2 = $count > 4 ? intval($matches[4]) : 0;
  2431. $sec2 = $count > 5 ? intval($matches[5]) : 0;
  2432. if (empty($date1) || empty($date2)) {
  2433. return FALSE;
  2434. }
  2435. if (empty($time1) || empty($time2)) {
  2436. return FALSE;
  2437. }
  2438. $tmp = date_seconds('s', TRUE, $increment);
  2439. $max_seconds = intval(array_pop($tmp));
  2440. $tmp = date_minutes('i', TRUE, $increment);
  2441. $max_minutes = intval(array_pop($tmp));
  2442. // See if minutes and seconds are the maximum allowed for an increment or the
  2443. // maximum possible (59), or 0.
  2444. switch ($granularity) {
  2445. case 'second':
  2446. $min_match = $time1 == '00:00:00'
  2447. || ($hour1 == 0 && $min1 == 0 && $sec1 == 0);
  2448. $max_match = $time2 == '00:00:00'
  2449. || ($hour2 == 23 && in_array($min2, array($max_minutes, 59)) && in_array($sec2, array($max_seconds, 59)))
  2450. || ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0 && $sec1 == 0 && $sec2 == 0);
  2451. break;
  2452. case 'minute':
  2453. $min_match = $time1 == '00:00:00'
  2454. || ($hour1 == 0 && $min1 == 0);
  2455. $max_match = $time2 == '00:00:00'
  2456. || ($hour2 == 23 && in_array($min2, array($max_minutes, 59)))
  2457. || ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0);
  2458. break;
  2459. case 'hour':
  2460. $min_match = $time1 == '00:00:00'
  2461. || ($hour1 == 0);
  2462. $max_match = $time2 == '00:00:00'
  2463. || ($hour2 == 23)
  2464. || ($hour1 == 0 && $hour2 == 0);
  2465. break;
  2466. default:
  2467. $min_match = TRUE;
  2468. $max_match = FALSE;
  2469. }
  2470. if ($min_match && $max_match) {
  2471. return TRUE;
  2472. }
  2473. return FALSE;
  2474. }
  2475. /**
  2476. * Helper function to round minutes and seconds to requested value.
  2477. */
  2478. function date_increment_round(&$date, $increment) {
  2479. // Round minutes and seconds, if necessary.
  2480. if (is_object($date) && $increment > 1) {
  2481. $day = intval(date_format($date, 'j'));
  2482. $hour = intval(date_format($date, 'H'));
  2483. $second = intval(round(intval(date_format($date, 's')) / $increment) * $increment);
  2484. $minute = intval(date_format($date, 'i'));
  2485. if ($second == 60) {
  2486. $minute += 1;
  2487. $second = 0;
  2488. }
  2489. $minute = intval(round($minute / $increment) * $increment);
  2490. if ($minute == 60) {
  2491. $hour += 1;
  2492. $minute = 0;
  2493. }
  2494. date_time_set($date, $hour, $minute, $second);
  2495. if ($hour == 24) {
  2496. $day += 1;
  2497. $hour = 0;
  2498. $year = date_format($date, 'Y');
  2499. $month = date_format($date, 'n');
  2500. date_date_set($date, $year, $month, $day);
  2501. }
  2502. }
  2503. return $date;
  2504. }
  2505. /**
  2506. * Determines if a date object is valid.
  2507. *
  2508. * @param object $date
  2509. * The date object to check.
  2510. *
  2511. * @return bool
  2512. * TRUE if the date is a valid date object, FALSE otherwise.
  2513. */
  2514. function date_is_date($date) {
  2515. if (empty($date) || !is_object($date) || !empty($date->errors)) {
  2516. return FALSE;
  2517. }
  2518. return TRUE;
  2519. }
  2520. /**
  2521. * This function will replace ISO values that have the pattern 9999-00-00T00:00:00
  2522. * with a pattern like 9999-01-01T00:00:00, to match the behavior of non-ISO
  2523. * dates and ensure that date objects created from this value contain a valid month
  2524. * and day. Without this fix, the ISO date '2020-00-00T00:00:00' would be created as
  2525. * November 30, 2019 (the previous day in the previous month).
  2526. *
  2527. * @param string $iso_string
  2528. * An ISO string that needs to be made into a complete, valid date.
  2529. *
  2530. * @TODO Expand on this to work with all sorts of partial ISO dates.
  2531. */
  2532. function date_make_iso_valid($iso_string) {
  2533. // If this isn't a value that uses an ISO pattern, there is nothing to do.
  2534. if (is_numeric($iso_string) || !preg_match(DATE_REGEX_ISO, $iso_string)) {
  2535. return $iso_string;
  2536. }
  2537. // First see if month and day parts are '-00-00'.
  2538. if (substr($iso_string, 4, 6) == '-00-00') {
  2539. return preg_replace('/([\d]{4}-)(00-00)(T[\d]{2}:[\d]{2}:[\d]{2})/', '${1}01-01${3}', $iso_string);
  2540. }
  2541. // Then see if the day part is '-00'.
  2542. elseif (substr($iso_string, 7, 3) == '-00') {
  2543. return preg_replace('/([\d]{4}-[\d]{2}-)(00)(T[\d]{2}:[\d]{2}:[\d]{2})/', '${1}01${3}', $iso_string);
  2544. }
  2545. // Fall through, no changes required.
  2546. return $iso_string;
  2547. }