[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/sites/all/modules/date/ -> date_api.module (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  /**
  11   * Set up some constants.
  12   *
  13   * Includes standard date types, format strings, strict regex strings for ISO
  14   * and DATETIME formats (seconds are optional).
  15   *
  16   * The loose regex will find any variety of ISO date and time, with or
  17   * without time, with or without dashes and colons separating the elements,
  18   * and with either a 'T' or a space separating date and time.
  19   */
  20  define('DATE_ISO',  'date');
  21  define('DATE_UNIX', 'datestamp');
  22  define('DATE_DATETIME', 'datetime');
  23  define('DATE_ARRAY', 'array');
  24  define('DATE_OBJECT', 'object');
  25  define('DATE_ICAL', 'ical');
  26  
  27  define('DATE_FORMAT_ISO', "Y-m-d\TH:i:s");
  28  define('DATE_FORMAT_UNIX', "U");
  29  define('DATE_FORMAT_DATETIME', "Y-m-d H:i:s");
  30  define('DATE_FORMAT_ICAL', "Ymd\THis");
  31  define('DATE_FORMAT_ICAL_DATE', "Ymd");
  32  define('DATE_FORMAT_DATE', 'Y-m-d');
  33  
  34  define('DATE_REGEX_ISO', '/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):?(\d{2})?/');
  35  define('DATE_REGEX_DATETIME', '/(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):?(\d{2})?/');
  36  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})?)?/');
  37  define('DATE_REGEX_ICAL_DATE', '/(\d{4})(\d{2})(\d{2})/');
  38  define('DATE_REGEX_ICAL_DATETIME', '/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?/');
  39  
  40  /**
  41   * Implementation of hook_init().
  42   */
  43  function date_api_init() {
  44    if (function_exists('date_default_timezone_set')) {
  45      date_default_timezone_set(date_default_timezone_name());
  46    }  
  47    drupal_add_css(drupal_get_path('module', 'date_api')  .'/date.css');
  48  
  49  }
  50  
  51  /**
  52   * Implementation of hook_menu().
  53   */
  54  function date_api_menu() {
  55    $items = array();
  56    $items['admin/settings/date-time/formats'] = array(
  57      'title' => 'Formats',
  58      'description' => 'Allow users to configure date formats',
  59      'type' => MENU_LOCAL_TASK,
  60      'file' => 'date_api.admin.inc',
  61      'page callback' => 'drupal_get_form',
  62      'page arguments' => array('date_api_date_formats_form'),
  63      'access arguments' => array('administer site configuration'),
  64      'weight' => 1,
  65    );
  66    $items['admin/settings/date-time/formats/configure'] = array(
  67      'title' => 'Configure',
  68      'description' => 'Allow users to configure date formats',
  69      'type' => MENU_DEFAULT_LOCAL_TASK,
  70      'file' => 'date_api.admin.inc',
  71      'page callback' => 'drupal_get_form',
  72      'page arguments' => array('date_api_date_formats_form'),
  73      'access arguments' => array('administer site configuration'),
  74      'weight' => 1,
  75    );
  76    $items['admin/settings/date-time/formats/lookup'] = array(
  77      'title' => 'Date and time lookup',
  78      'type' => MENU_CALLBACK,
  79      'page callback' => 'date_api_date_time_lookup',
  80      'access arguments' => array('administer site configuration'),
  81    );
  82    $items['admin/settings/date-time/formats/custom'] = array(
  83      'title' => 'Custom formats',
  84      'description' => 'Allow users to configure custom date formats.',
  85      'type' => MENU_LOCAL_TASK,
  86      'file' => 'date_api.admin.inc',
  87      'page callback' => 'date_api_configure_custom_date_formats',
  88      'access arguments' => array('administer site configuration'),
  89      'weight' => 2,
  90    );
  91    $items['admin/settings/date-time/formats/add'] = array(
  92      'title' => 'Add format',
  93      'description' => 'Allow users to add additional date formats.',
  94      'type' => MENU_LOCAL_TASK,
  95      'file' => 'date_api.admin.inc',
  96      'page callback' => 'drupal_get_form',
  97      'page arguments' => array('date_api_add_date_formats_form'),
  98      'access arguments' => array('administer site configuration'),
  99      'weight' => 3,
 100    );
 101    $items['admin/settings/date-time/formats/delete/%'] = array(
 102      'title' => 'Delete date format',
 103      'description' => 'Allow users to delete a configured date format.',
 104      'type' => MENU_CALLBACK,
 105      'file' => 'date_api.admin.inc',
 106      'page callback' => 'drupal_get_form',
 107      'page arguments' => array('date_api_delete_format_form', 5),
 108      'access arguments' => array('administer site configuration'),
 109    );
 110    $items['admin/settings/date-time/delete/%'] = array(
 111      'title' => 'Delete date format type',
 112      'description' => 'Allow users to delete a configured date format type.',
 113      'type' => MENU_CALLBACK,
 114      'file' => 'date_api.admin.inc',
 115      'page callback' => 'drupal_get_form',
 116      'page arguments' => array('date_api_delete_format_type_form', 4),
 117      'access arguments' => array('administer site configuration'),
 118    );
 119  
 120    return $items;
 121  }
 122  
 123  /**
 124   * Implementation of hook_menu_alter().
 125   */
 126  function date_api_menu_alter(&$callbacks) {
 127    // Add a new 'admin/settings/date-time/configure' path and make it the same as
 128    // the 'admin/settings/date-time'.  Also set it to be the default local task -
 129    // needed to make tabs work as expected.
 130    $callbacks['admin/settings/date-time/configure'] = $callbacks['admin/settings/date-time'];
 131    $callbacks['admin/settings/date-time/configure']['type'] = MENU_DEFAULT_LOCAL_TASK;
 132  }
 133  
 134  /**
 135   * Helper function for getting the format string for a date type.
 136   */
 137  function date_type_format($type) {
 138    switch ($type) {
 139      case DATE_ISO:
 140        return DATE_FORMAT_ISO;
 141      case DATE_UNIX:
 142        return DATE_FORMAT_UNIX;
 143      case DATE_DATETIME:
 144        return DATE_FORMAT_DATETIME;
 145      case DATE_ICAL:
 146        return DATE_FORMAT_ICAL;
 147    }
 148  }
 149  
 150  /**
 151   * An untranslated array of month names
 152   *
 153   * Needed for css, translation functions, strtotime(), and other places
 154   * that use the English versions of these words.
 155   *
 156   * @return
 157   *   an array of month names
 158   */
 159  function date_month_names_untranslated() {
 160    static $month_names;
 161    if (empty($month_names)) {
 162      $month_names = array(1 => 'January', 2 => 'February', 3 => 'March',
 163        4 => 'April', 5 => 'May', 6 => 'June', 7 => 'July',
 164        8 => 'August', 9 => 'September', 10 => 'October',
 165        11 => 'November', 12 => 'December');
 166    }
 167    return $month_names;
 168  }
 169  
 170  /**
 171   * A translated array of month names
 172   *
 173   * @param $required
 174   *   If not required, will include a blank value at the beginning of the list.
 175   * @return
 176   *   an array of month names
 177   */
 178  function date_month_names($required = FALSE) {
 179    $month_names = array();
 180    foreach (date_month_names_untranslated() as $key => $day) {
 181      $month_names[$key] = date_t($day, 'month_name');
 182    }
 183    $none = array('' => '');
 184    return !$required ? $none + $month_names : $month_names;
 185  }
 186  
 187  /**
 188   * A translated array of month name abbreviations
 189   *
 190   * @param $required
 191   *   If not required, will include a blank value at the beginning of the list.
 192   * @return
 193   *   an array of month abbreviations
 194   */
 195  function date_month_names_abbr($required = FALSE) {
 196    $month_names = array();
 197    foreach (date_month_names_untranslated() as $key => $day) {
 198      $month_names[$key] = date_t($day, 'month_abbr');
 199    }
 200    $none = array('' => '');
 201    return !$required ? $none + $month_names : $month_names;
 202  }
 203  
 204  /**
 205   * An untranslated array of week days
 206   *
 207   * Needed for css, translation functions, strtotime(), and other places
 208   * that use the English versions of these words.
 209   *
 210   * @return
 211   *   an array of week day names
 212   */
 213  function date_week_days_untranslated($refresh = TRUE) {
 214    static $weekdays;
 215    if ($refresh || empty($weekdays)) {
 216      $weekdays = array(0 => 'Sunday', 1 => 'Monday', 2 => 'Tuesday',
 217        3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday',
 218        6 => 'Saturday');
 219    }
 220    return $weekdays;
 221  }
 222  
 223  /**
 224   * A translated array of week days
 225   *
 226   * @param $required
 227   *   If not required, will include a blank value at the beginning of the array.
 228   * @return
 229   *   an array of week day names
 230   */
 231  function date_week_days($required = FALSE, $refresh = TRUE) {
 232    $weekdays = array();
 233    foreach (date_week_days_untranslated() as $key => $day) {
 234      $weekdays[$key] = date_t($day, 'day_name');
 235    }
 236    $none = array('' => '');
 237    return !$required ? $none + $weekdays : $weekdays;
 238  }
 239  
 240  /**
 241   * An translated array of week day abbreviations.
 242   *
 243   * @param $required
 244   *   If not required, will include a blank value at the beginning of the array.
 245   * @return
 246   *   an array of week day abbreviations
 247   */
 248  function date_week_days_abbr($required = FALSE, $refresh = TRUE, $length = 3) {
 249    $weekdays = array();
 250    switch ($length) {
 251      case 1:
 252        $context = 'day_abbr1';
 253        break;
 254      case 2:
 255        $context = 'day_abbr2';
 256        break;
 257      default:
 258        $context = 'day_abbr';
 259        break; 
 260    }
 261    foreach (date_week_days_untranslated() as $key => $day) {
 262      $weekdays[$key] = date_t($day, $context);
 263    }
 264    $none = array('' => '');
 265    return !$required ? $none + $weekdays : $weekdays;
 266  }
 267  
 268  /**
 269   * Order weekdays
 270   *   Correct weekdays array so first day in array matches the first day of
 271   *   the week. Use to create things like calendar headers.
 272   *
 273   * @param array $weekdays
 274   * @return array
 275   */
 276  function date_week_days_ordered($weekdays) {
 277    if (variable_get('date_first_day', 1) > 0) {
 278      for ($i = 1; $i <= variable_get('date_first_day', 1); $i++) {
 279        $last = array_shift($weekdays);
 280        array_push($weekdays, $last);
 281      }
 282    }
 283    return $weekdays;
 284  }
 285  
 286  /**
 287   * An array of years.
 288   *
 289   * @param int $min
 290   *   the minimum year in the array
 291   * @param int $max
 292   *   the maximum year in the array
 293   * @param $required
 294   *   If not required, will include a blank value at the beginning of the array.
 295   * @return
 296   *   an array of years in the selected range
 297   */
 298  function date_years($min = 0, $max = 0, $required = FALSE) {
 299    // Have to be sure $min and $max are valid values;
 300    if (empty($min)) $min = intval(date('Y', time()) - 3);
 301    if (empty($max)) $max = intval(date('Y', time()) + 3);
 302    $none = array(0 => '');
 303    return !$required ? $none + drupal_map_assoc(range($min, $max)) : drupal_map_assoc(range($min, $max));
 304  }
 305  
 306  /**
 307   * An array of days.
 308   *
 309   * @param $required
 310   *   If not required, returned array will include a blank value.
 311   * @param integer $month (optional)
 312   * @param integer $year (optional)
 313   * @return
 314   *   an array of days for the selected month.
 315   */
 316  function date_days($required = FALSE, $month = NULL, $year = NULL) {
 317    // If we have a month and year, find the right last day of the month.
 318    if (!empty($month) && !empty($year)) {
 319      $date = date_make_date($year .'-'. $month .'-01 00:00:00', 'UTC');
 320      $max = date_format('t', $date);
 321    }
 322    // If there is no month and year given, default to 31.
 323    if (empty($max)) $max = 31;
 324    $none = array(0 => '');
 325    return !$required ? $none + drupal_map_assoc(range(1, $max)) : drupal_map_assoc(range(1, $max));
 326  }
 327  
 328  /**
 329   * An array of hours.
 330   *
 331   * @param string $format
 332   * @param $required
 333   *   If not required, returned array will include a blank value.
 334   * @return
 335   *   an array of hours in the selected format.
 336   */
 337  function date_hours($format = 'H', $required = FALSE) {
 338    $hours = array();
 339    if ($format == 'h' || $format == 'g') {
 340      $min = 1;
 341      $max = 12;
 342    }
 343    else  {
 344      $min = 0;
 345      $max = 23;
 346    }
 347    for ($i = $min; $i <= $max; $i++) {
 348      $hours[$i] = $i < 10 && ($format == 'H' || $format == 'h') ? "0$i" : $i;
 349    }
 350    $none = array('' => '');
 351    return !$required ? $none + $hours : $hours;
 352  }
 353  
 354  /**
 355   * An array of minutes.
 356   *
 357   * @param string $format
 358   * @param $required
 359   *   If not required, returned array will include a blank value.
 360   * @return
 361   *   an array of minutes in the selected format.
 362   */
 363  function date_minutes($format = 'i', $required = FALSE, $increment = 1) {
 364    $minutes = array();
 365    // Have to be sure $increment has a value so we don't loop endlessly;
 366    if (empty($increment)) $increment = 1;
 367    for ($i = 0; $i < 60; $i += $increment) {
 368      $minutes[$i] = $i < 10 && $format == 'i' ? "0$i" : $i;
 369    }
 370    $none = array('' => '');
 371    return !$required ? $none + $minutes : $minutes;
 372  }
 373  
 374  /**
 375   * An array of seconds.
 376   *
 377   * @param string $format
 378   * @param $required
 379   *   If not required, returned array will include a blank value.
 380   * @return array an array of seconds in the selected format.
 381   */
 382  function date_seconds($format = 's', $required = FALSE, $increment = 1) {
 383    $seconds = array();
 384    // Have to be sure $increment has a value so we don't loop endlessly;
 385    if (empty($increment)) $increment = 1;
 386    for ($i = 0; $i < 60; $i += $increment) {
 387      $seconds[$i] = $i < 10 && $format == 's' ? "0$i" : $i;
 388    }
 389    $none = array('' => '');
 390    return !$required ? $none + $seconds : $seconds;
 391  }
 392  
 393  /**
 394   * An array of am and pm options.
 395   * @param $required
 396   *   If not required, returned array will include a blank value.
 397   * @return array an array of am pm options.
 398   */
 399  function date_ampm($required = FALSE) {
 400    $none = array('' => '');
 401    $ampm = array('am' => date_t('am', 'ampm'), 'pm' => date_t('pm', 'ampm'));
 402    return !$required ? $none + $ampm : $ampm;
 403  }
 404  
 405  /**
 406   * Implementation of hook_date_formats().
 407   *
 408   * @return
 409   *   An array of date formats with attributes 'type' (short, medium or long),
 410   *   'format' (the format string) and 'locales'.  The 'locales' attribute is an
 411   *   array of locales, which can include both 2 character language codes like
 412   *   'en', 'fr', but also 5 character language codes like 'en-gb' and 'en-us'.
 413   */
 414  function date_api_date_formats() {
 415    include_once('./'. drupal_get_path('module', 'date_api') .'/date_api_formats_list.inc');
 416    return _date_api_date_formats_list();
 417  }
 418  
 419  /**
 420   * Implementation of hook_date_format_types().
 421   */
 422  function date_api_date_format_types() {
 423    return array(
 424      'long' => t('Long'),
 425      'medium' => t('Medium'),
 426      'short' => t('Short'),
 427    );
 428  }
 429  
 430  /**
 431   * Array of regex replacement strings for date format elements.
 432   * Used to allow input in custom formats. Based on work done for
 433   * the Date module by Yves Chedemois (yched).
 434   *
 435   * @return array of date() format letters and their regex equivalents.
 436   */
 437  function date_format_patterns($strict = FALSE) {
 438    return array(
 439      'd' => '\d{'. ($strict ? '2' : '1,2') .'}', 
 440      'm' => '\d{'. ($strict ? '2' : '1,2') .'}', 
 441      'h' => '\d{'. ($strict ? '2' : '1,2') .'}',      
 442      'H' => '\d{'. ($strict ? '2' : '1,2') .'}',   
 443      'i' => '\d{'. ($strict ? '2' : '1,2') .'}',
 444      's' => '\d{'. ($strict ? '2' : '1,2') .'}',
 445      'j' => '\d{1,2}',    'N' => '\d',      'S' => '\w{2}',
 446      'w' => '\d',       'z' => '\d{1,3}',    'W' => '\d{1,2}',
 447      'n' => '\d{1,2}',  't' => '\d{2}',      'L' => '\d',      'o' => '\d{4}',
 448      'Y' => '\d{4}',    'y' => '\d{2}',      'B' => '\d{3}',   'g' => '\d{1,2}',
 449      'G' => '\d{1,2}',  'e' => '\w*',        'I' => '\d',      'T' => '\w*',
 450      'U' => '\d*',      'z' => '[+-]?\d*',   'O' => '[+-]?\d{4}',
 451      //Using S instead of w and 3 as well as 4 to pick up non-ASCII chars like German umlaute.
 452      // Per http://drupal.org/node/1101294, we may need as little as 2 and as many as 5 characters
 453      // in some languages.
 454      'D' => '\S{2,5}',    'l' => '\S*', 'M' => '\S{2,5}', 'F' => '\S*',
 455      'P' => '[+-]?\d{2}\:\d{2}',
 456      'c' => '(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-]?\d{2}\:\d{2})',
 457      'r' => '(\w{3}), (\d{2})\s(\w{3})\s(\d{2,4})\s(\d{2}):(\d{2}):(\d{2})([+-]?\d{4})?',
 458      );
 459  }
 460  
 461  /**
 462   * Array of granularity options and their labels
 463   *
 464   * @return array
 465   */
 466  function date_granularity_names() {
 467    return array(
 468      'year' => date_t('Year', 'datetime'), 
 469      'month' => date_t('Month', 'datetime'), 
 470      'day' => date_t('Day', 'datetime'),
 471      'hour' => date_t('Hour', 'datetime'), 
 472      'minute' => date_t('Minute', 'datetime'), 
 473      'second' => date_t('Second', 'datetime'),
 474      );
 475  }
 476  
 477  /**
 478   * Sort a granularity array.
 479   */
 480  function date_granularity_sorted($granularity) {
 481    return array_intersect(array('year', 'month', 'day', 'hour', 'minute', 'second'), $granularity);
 482  }
 483  
 484  /**
 485   * Give a granularity $precision, return an array of 
 486   * all the possible granularity elements.
 487   */
 488  function date_granularity_array_from_precision($precision) {
 489    $granularity_array = array('year', 'month', 'day', 'hour', 'minute', 'second');
 490    switch(($precision)) {
 491      case 'year':
 492        return array_slice($granularity_array, -6);
 493      case 'month':
 494        return array_slice($granularity_array, -5);
 495      case 'day':
 496        return array_slice($granularity_array, -4);
 497      case 'hour':
 498        return array_slice($granularity_array, -3);
 499      case 'minute':
 500        return array_slice($granularity_array, -2);
 501      default:
 502        return $granularity_array;
 503    }
 504  }
 505  
 506  /**
 507   * Give a granularity array, return the highest precision.
 508   */
 509  function date_granularity_precision($granularity_array) {
 510    $input = clone($granularity_array);
 511    return array_pop($input);
 512  }
 513  
 514  /**
 515   * Construct an appropriate DATETIME format string for the granularity of an item.
 516   */
 517  function date_granularity_format($granularity) {
 518    if (is_array($granularity)) {
 519      $granularity = date_granularity_precision($granularity);
 520    }
 521    $format = 'Y-m-d H:i:s';
 522    switch ($granularity) {
 523      case 'year':
 524        return substr($format, 0, 1);
 525      case 'month':
 526        return substr($format, 0, 3);
 527      case 'day':
 528        return substr($format, 0, 5);
 529      case 'hour';
 530        return substr($format, 0, 7);
 531      case 'minute':
 532        return substr($format, 0, 9);
 533      default:
 534        return $format;
 535    } 
 536  }
 537  
 538  /**
 539   * A translated array of timezone names.
 540   * Cache the untranslated array, make the translated array a static variable.
 541   *
 542   * @param $required
 543   *   If not required, returned array will include a blank value.
 544   * @return
 545   *   an array of timezone names
 546   */
 547  function date_timezone_names($required = FALSE, $refresh = FALSE) {
 548    static $zonenames;
 549    if (empty($zonenames) || $refresh) {
 550      $cached = cache_get('date_timezone_identifiers_list');
 551      $zonenames = !empty($cached) ? $cached->data : array();
 552      if ($refresh || empty($cached) || empty($zonenames)) {
 553        $data = timezone_identifiers_list();
 554        asort($data);
 555        // Use include instead of include once in case the function gets 
 556        // refreshed via devel or other API and is called more than once.
 557        if (module_exists('date_php4')) {
 558          include('./'. drupal_get_path('module', 'date_php4') .'/date_php4_missing_data.inc');
 559        }
 560        foreach ($data as $delta => $zone) {
 561          // Because many time zones exist in PHP only for backward 
 562          // compatibility reasons and should not be used, the list is 
 563          // filtered by a regular expression.
 564          if (preg_match('!^((Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/|UTC$)!', $zone)) {
 565            $zonenames[$zone] = $zone;
 566          }      
 567        }
 568        // If using PHP4, filter the list down to only the timezones
 569        // the PHP4 wrapper has data for.
 570        if (module_exists('date_php4')) {
 571          foreach ($missing_timezone_data as $zone) {
 572            unset($zonenames[$zone]);
 573          }
 574        }
 575  
 576        // If using Event, further filter the list down to only 
 577        // zones that exist in the event module.
 578        if (module_exists('event') && db_table_exists('event_timezones')) {
 579          $result = db_query("SELECT name FROM {event_timezones} ORDER BY name");
 580          $names = array();
 581          while ($row = db_fetch_array($result)) {
 582            $names[] = str_replace(' ', '_', $row['name']);
 583          }
 584          foreach ($zonenames as $name => $zone) {
 585            if (!in_array($name, $names)) {
 586              unset($zonenames[$name]);
 587            }
 588          }
 589        }
 590        if (!empty($zonenames)) {
 591          cache_set('date_timezone_identifiers_list', $zonenames);
 592        }
 593      }
 594      foreach ($zonenames as $zone) {
 595        $zonenames[$zone] = t('!timezone', array('!timezone' => $zone));
 596      }
 597    }
 598    $none = array('' => '');
 599    return !$required ? $none + $zonenames : $zonenames;
 600  }
 601  
 602  /**
 603   * An array of timezone abbreviations that the system allows.
 604   * Cache an array of just the abbreviation names because the
 605   * whole timezone_abbreviations_list is huge so we don't want
 606   * to get it more than necessary.
 607   *
 608   * @return array
 609   */
 610  function date_timezone_abbr($refresh = FALSE) {
 611    $cached = cache_get('date_timezone_abbreviations');
 612    $data = isset($cached->data) ? $cached->data : array();
 613    if (empty($data) || $refresh) {
 614      $data = array_keys(timezone_abbreviations_list());
 615      cache_set('date_timezone_abbreviations', $data);
 616    }
 617    return $data;
 618  }
 619  
 620  /**
 621   * A function to translate ambiguous short date strings.
 622   * 
 623   * Example: Pass in 'Monday', 'day_abbr' and get the translated 
 624   * abbreviation for Monday.
 625   * 
 626   * @param string $string
 627   * @param string $context
 628   * @param int $langcode
 629   * @return translated value of the string
 630   */
 631  function date_t($string, $context, $langcode = NULL) {
 632    static $replace = array();
 633  
 634    if (empty($replace[$langcode])) {
 635      // The function to create the date string arrays is kept separate
 636      // so those arrays can be directly accessed by other functions.
 637      date_t_strings($replace, $langcode);
 638    }
 639    switch ($context) {
 640      case 'day_name':
 641      case 'day_abbr':
 642      case 'day_abbr1':
 643      case 'day_abbr2':  
 644        $untranslated = array_flip(date_week_days_untranslated());
 645        break;
 646      case 'month_name':
 647      case 'month_abbr':
 648        $untranslated = array_flip(date_month_names_untranslated());
 649        break;  
 650      case 'ampm':
 651        $untranslated = array_flip(array('am', 'pm', 'AM', 'PM'));  
 652        break;
 653      case 'datetime':
 654        $untranslated = array_flip(array('Year', 'Month', 'Day', 'Week', 'Hour', 'Minute', 'Second', 'All Day', 'All day'));  
 655        break;
 656      case 'datetime_plural':
 657        $untranslated = array_flip(array('Years', 'Months', 'Days', 'Weeks', 'Hours', 'Minutes', 'Seconds'));  
 658        break;
 659      case 'date_order':
 660        $untranslated = array_flip(array('Every', 'First', 'Second', 'Third', 'Fourth', 'Fifth'));  
 661        break;
 662      case 'date_order_reverse':
 663        $untranslated = array_flip(array('', 'Last', 'Next to last', 'Third from last', 'Fourth from last', 'Fifth from last'));  
 664        break;
 665      case 'date_nav':
 666        $untranslated = array_flip(array('Prev', 'Next', 'Today'));  
 667        break;
 668    }
 669    $pos = $untranslated[$string];
 670    return $replace[$langcode][$context][$pos];
 671  }
 672  
 673  /**
 674   * Construct translation arrays from pipe-delimited strings.
 675   * 
 676   * Combining these strings into a single t() gives them the context needed
 677   * for better translation.
 678   */
 679  function date_t_strings(&$replace, $langcode) {
 680    $replace[$langcode]['day_name'] = explode('|', trim(t('!day-name Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday', array('!day-name' => ''), $langcode)));
 681    $replace[$langcode]['day_abbr'] = explode('|', trim(t('!day-abbreviation Sun|Mon|Tue|Wed|Thu|Fri|Sat', array('!day-abbreviation' => ''), $langcode)));
 682    $replace[$langcode]['day_abbr1'] = explode('|', trim(t('!day-abbreviation S|M|T|W|T|F|S', array('!day-abbreviation' => ''), $langcode)));
 683    $replace[$langcode]['day_abbr2'] = explode('|', trim(t('!day-abbreviation SU|MO|TU|WE|TH|FR|SA', array('!day-abbreviation' => ''), $langcode)));
 684    $replace[$langcode]['ampm'] = explode('|', trim(t('!ampm-abbreviation am|pm|AM|PM', array('!ampm-abbreviation' => ''), $langcode)));
 685    $replace[$langcode]['datetime'] = explode('|', trim(t('!datetime Year|Month|Day|Week|Hour|Minute|Second|All Day|All day', array('!datetime' => ''), $langcode)));
 686    $replace[$langcode]['datetime_plural'] = explode('|', trim(t('!datetime_plural Years|Months|Days|Weeks|Hours|Minutes|Seconds', array('!datetime_plural' => ''), $langcode)));
 687    $replace[$langcode]['date_order'] = explode('|', trim(t('!date_order Every|First|Second|Third|Fourth|Fifth', array('!date_order' => ''), $langcode)));
 688    $replace[$langcode]['date_order_reverse'] = explode('|', trim(t('!date_order |Last|Next to last|Third from last|Fourth from last|Fifth from last', array('!date_order' => ''), $langcode)));
 689    $replace[$langcode]['date_nav'] = explode('|', trim(t('!date_nav Prev|Next|Today', array('!date_nav' => ''), $langcode)));
 690  
 691    // These start with a pipe so the January value will be in position 1 instead of position 0.
 692    $replace[$langcode]['month_name'] = explode('|', trim(t('!month-name |January|February|March|April|May|June|July|August|September|October|November|December', array('!month-name' => ''), $langcode)));
 693    $replace[$langcode]['month_abbr'] = explode('|', trim(t('!month-abbreviation |Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec', array('!month-abbreviation' => ''), $langcode)));
 694  }
 695  
 696  /**
 697   * Reworked from Drupal's format_date function to handle pre-1970 and
 698   * post-2038 dates and accept a date object instead of a timestamp as input.
 699   *
 700   * Translates formatted date results, unlike PHP function date_format().
 701   *
 702   * @param $oject
 703   *   A date object, could be created by date_make_date().
 704   * @param $type
 705   *   The format to use. Can be "small", "medium" or "large" for the preconfigured
 706   *   date formats. If "custom" is specified, then $format is required as well.
 707   * @param $format
 708   *   A PHP date format string as required by date(). A backslash should be used
 709   *   before a character to avoid interpreting the character as part of a date
 710   *   format.
 711   * @return
 712   *   A translated date string in the requested format.
 713   */
 714  function date_format_date($date, $type = 'medium', $format = '', $langcode = NULL) {
 715    if (empty($date)) {
 716      return '';
 717    }
 718  
 719    if (function_exists('timezone_name_from_abbr') && get_class($date) != 'DateTime') {
 720      $date = date_make_date($date);
 721    }
 722    switch ($type) {
 723      case 'small':
 724      case 'short':
 725        $format = variable_get('date_format_short', 'm/d/Y - H:i');
 726        break;
 727      case 'large':
 728      case 'long':
 729        $format = variable_get('date_format_long', 'l, F j, Y - H:i');
 730        break;
 731      case 'custom':
 732        $format = $format;
 733        break;
 734      case 'medium':
 735      default:
 736        $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
 737    }
 738    $max = strlen($format);
 739    $datestring = '';
 740    for ($i = 0; $i < $max; $i++) {
 741      $c = $format[$i];
 742      switch ($c) {
 743        // Use date_t() for ambiguous short strings that need translation.
 744        // We send long day and month names to date_t(), along with context.
 745        case 'l':
 746          $datestring .= date_t(date_format($date, 'l'), 'day_name', $langcode);
 747          break;
 748        case 'D':
 749          $datestring .= date_t(date_format($date, 'l'), 'day_abbr', $langcode);
 750          break;
 751        case 'F':
 752          $datestring .= date_t(date_format($date, 'F'), 'month_name', $langcode);
 753          break;  
 754        case 'M':
 755          $datestring .= date_t(date_format($date, 'F'), 'month_abbr', $langcode);
 756          break;  
 757        case 'A':
 758        case 'a':
 759          $datestring .= date_t(date_format($date, $c), 'ampm', $langcode);
 760          break;  
 761        // The timezone name translations can use t().  
 762        case 'e':
 763        case 'T':
 764          $datestring .= t(date_format($date, $c));
 765          break;
 766        // Remaining date parts need no translation.
 767        case 'O':
 768          $datestring .= sprintf('%s%02d%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60);
 769          break;
 770        case 'P':
 771          $datestring .= sprintf('%s%02d:%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60);
 772          break;
 773        case 'Z':
 774          $datestring .= date_offset_get($date);
 775          break;              
 776        case '\\':
 777          $datestring .= $format[++$i];
 778          break;
 779        case 'r':
 780          $datestring .= date_format_date($date, 'custom', 'D, d M Y H:i:s O', $langcode);
 781          break;
 782        default:
 783          if (strpos('BdcgGhHiIjLmnNosStTuUwWYyz', $c) !== FALSE) {
 784            $datestring .= date_format($date, $c);
 785          }
 786          else {
 787            $datestring .= $c;
 788          }
 789      }
 790    }
 791    return $datestring;
 792  }
 793  
 794  /**
 795   * An override for interval formatting that adds past and future context
 796   *
 797   * @param DateTime $date
 798   * @param integer $granularity
 799   * @return formatted string
 800   */
 801  function date_format_interval($date, $granularity = 2) {
 802    // If no date is sent, then return nothing
 803    if (empty($date)){
 804      return NULL;
 805    }
 806  
 807    $interval = time() - date_format($date, 'U');
 808    if($interval > 0 ) {
 809      return t('!time ago', array('!time' => format_interval($interval, $granularity)));
 810    } 
 811    else {
 812      return format_interval(abs($interval), $granularity);
 813    }
 814  }
 815  
 816  /**
 817   * A date object for the current time.
 818   *
 819   * @param $timezone
 820   *   Optional method to force time to a specific timezone,
 821   *   defaults to user timezone, if set, otherwise site timezone.
 822   * @return object date
 823   */
 824  function date_now($timezone = NULL) {
 825    return date_make_date('now', $timezone);
 826  }
 827  
 828  /**
 829   *  Convert a date of any type or an array of date parts into a valid date
 830   *  object.
 831  
 832   *  @param $date
 833   *    A date in any format or the string 'now'.
 834   *  @param $timezone
 835   *    Optional, the name of the timezone this date is in, defaults
 836   *    to the user timezone, if set, otherwise the site timezone.
 837   *    Accepts either a timezone name or a timezone object as input.
 838   *  @param $type
 839   *    The type of date provided, could be
 840   *    DATE_ARRAY, DATE_UNIX, DATE_DATETIME, DATE_ISO, or DATE_OBJECT.
 841   *  @param $granularity
 842   *    The granularity of the date value provided. Set this for partial
 843   *    dates so they pass validation and don't get reset to 'now'.
 844   */
 845  function date_make_date($date, $timezone = NULL, $type = DATE_DATETIME, $granularity = array('year', 'month', 'day', 'hour', 'minute')) {
 846  
 847    // Make sure some value is set for the date and timezone even if the
 848    // site timezone is not yet set up to avoid fatal installation
 849    // errors.
 850    if (empty($timezone) || !date_timezone_is_valid($timezone)) {
 851      $timezone = date_default_timezone_name();
 852    }
 853  
 854    // Special handling for a unix timestamp of '0', since it will fail 'empty' tests below.
 855    if ($date === 0 && $type == DATE_UNIX) {
 856      $date = date_convert($date, $type, DATE_DATETIME, $timezone);
 857      $type = DATE_DATETIME;
 858    }
 859  
 860    // No value or one with unexpected array keys.
 861    if (empty($date) || (is_array($date) && array_diff($granularity, array_keys($date)))) {
 862      return NULL;
 863    }
 864  
 865    // Special handling for partial dates that don't need precision.
 866    $max_granularity = array_pop(date_granularity_sorted($granularity));
 867    if (in_array($max_granularity, array('year', 'month')) || $type == DATE_ISO || $type == DATE_ARRAY) {
 868      if ($type == DATE_UNIX) {
 869        $date = date_convert($date, $type, DATE_DATETIME);
 870      }
 871      $date = date_fuzzy_datetime($date);
 872      $type = DATE_DATETIME;
 873    }
 874  
 875    if (!date_is_valid($date, $type, $granularity)) {
 876      $date = 'now';
 877    }
 878    if (!empty($timezone) && !empty($date)) {
 879      if ($date == 'now') {
 880        return date_create('now', timezone_open($timezone));
 881      }
 882      elseif ($datetime = date_convert($date, $type, DATE_DATETIME, $timezone)) {
 883        return date_create($datetime, timezone_open($timezone));
 884      }
 885    }
 886    return NULL;
 887  }
 888  
 889  function date_timezone_is_valid($timezone) {
 890    static $timezone_names;
 891    if (empty($timezone_names)) {
 892      $timezone_names = array_keys(date_timezone_names(TRUE));
 893    }
 894    if (!in_array($timezone, $timezone_names)) {
 895      return FALSE;
 896    }
 897    return TRUE;
 898  }
 899  
 900  /**
 901   * Return a timezone name to use as a default.
 902   *
 903   * @return a timezone name
 904   *   Identify the default timezone for a user, if available, otherwise the site.
 905   *   Must return a value even if no timezone info has been set up.
 906   */
 907  function date_default_timezone_name($check_user = TRUE) {
 908    global $user;
 909    if ($check_user && variable_get('configurable_timezones', 1) && !empty($user->timezone_name)) {
 910      return $user->timezone_name;
 911    }
 912    else {
 913      $default = variable_get('date_default_timezone_name', '');
 914      return empty($default) ? 'UTC' : $default;
 915    }
 916  }
 917  
 918  /**
 919   * A timezone object for the default timezone.
 920   *
 921   * @return a timezone name
 922   *   Identify the default timezone for a user, if available, otherwise the site.
 923   */
 924  function date_default_timezone($check_user = TRUE) {
 925    $timezone = date_default_timezone_name($check_user);
 926    return timezone_open(date_default_timezone_name($check_user));
 927  }
 928  
 929  /**
 930   * Identify the number of days in a month for a date.
 931   */
 932  function date_days_in_month($year, $month) {
 933    // Pick a day in the middle of the month to avoid timezone shifts.
 934    $datetime = date_pad($year, 4) .'-'. date_pad($month) .'-15 00:00:00';
 935    $date = date_make_date($datetime);
 936    return date_format($date, 't');
 937  }
 938  
 939  /**
 940   * Identify the number of days in a year for a date.
 941   *
 942   * @param mixed $date
 943   * @param string $type
 944   * @return integer
 945   */
 946  function date_days_in_year($date = NULL, $type = DATE_OBJECT) {
 947    if (empty($date)) {
 948      $date = date_now();
 949    }
 950    if (!is_object($date)) {
 951      $date = date_convert($date, $type, DATE_OBJECT);
 952    }  
 953    if (is_object($date)) {
 954      if (date_format($date, 'L')) {
 955        return 366;
 956      }
 957      else {
 958        return 365;
 959      }
 960    }
 961    return NULL;
 962  }
 963  
 964  /**
 965   * Identify the number of ISO weeks in a year for a date.
 966   *
 967   * December 28 is always in the last ISO week of the year.
 968   *
 969   * @param mixed $date
 970   * @param string $type
 971   * @return integer
 972   */
 973  function date_iso_weeks_in_year($date = NULL, $type = DATE_OBJECT) {
 974    if (empty($date)) {
 975      $date = date_now();
 976    }
 977    if (!is_object($date)) {
 978      $date = date_convert($date, $type, DATE_OBJECT);
 979    }  
 980    if (is_object($date)) {
 981      date_date_set($date, date_format($date, 'Y'), 12, 28);
 982      return date_format($date, 'W');
 983    }
 984    return NULL;
 985  }
 986  
 987  /**
 988   * Returns day of week for a given date (0 = Sunday).
 989   *
 990   * @param mixed  $date
 991   *   a date, default is current local day
 992   * @param string  $type
 993   *   The type of date, DATE_ISO, DATE_DATETIME, or DATE_UNIX
 994   * @return
 995   *    the number of the day in the week
 996   */
 997  function date_day_of_week($date = NULL, $type = DATE_OBJECT) {
 998    if (empty($date)) {
 999      $date = date_now();
1000      $type = DATE_OBJECT;
1001    }
1002    $date = date_convert($date, $type, DATE_OBJECT);
1003    if (is_object($date)) {
1004      return date_format($date, 'w');
1005    }
1006    return NULL;
1007  }
1008  
1009  /**
1010   * Returns translated name of the day of week for a given date.
1011   *
1012   * @param mixed  $date
1013   *   a date, default is current local day
1014   * @param string  $type
1015   *   The type of date, DATE_ISO, DATE_DATETIME, or DATE_UNIX
1016   * @param string $abbr
1017   *   Whether to return the abbreviated name for that day
1018   * @return
1019   *    the name of the day in the week for that date
1020   */
1021  function date_day_of_week_name($date = NULL, $abbr = TRUE, $type = DATE_DATETIME) {
1022    $dow = date_day_of_week($date, $type);
1023    $days = $abbr ? date_week_days_abbr() : date_week_days();
1024    return $days[$dow];
1025  }
1026  
1027  /**
1028   * Compute difference between two days using a given measure.
1029   *
1030   * @param mixed $date1
1031   *   the starting date
1032   * @param mixed $date2
1033   *   the ending date
1034   * @param string $measure
1035   *   'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'
1036   * @param string $type
1037   *   the type of dates provided:
1038   *   DATE_OBJECT, DATE_DATETIME, DATE_ISO, DATE_UNIX, DATE_ARRAY
1039   */
1040  function date_difference($date1_in, $date2_in, $measure = 'seconds', $type = DATE_OBJECT) {
1041    // Create cloned objects or original dates will be impacted by
1042    // the date_modify() operations done in this code.
1043    $date1 = drupal_clone(date_convert($date1_in, $type, DATE_OBJECT));
1044    $date2 = drupal_clone(date_convert($date2_in, $type, DATE_OBJECT));
1045    if (is_object($date1) && is_object($date2)) {
1046      $diff = date_format($date2, 'U') - date_format($date1, 'U');
1047      if ($diff == 0 ) {
1048        return 0;
1049      }
1050      elseif ($diff < 0) {
1051        // Make sure $date1 is the smaller date.
1052        $temp = $date2;
1053        $date2 = $date1;
1054        $date1 = $temp;
1055        $diff = date_format($date2, 'U') - date_format($date1, 'U');
1056      }
1057      $year_diff = intval(date_format($date2, 'Y') - date_format($date1, 'Y'));
1058      switch ($measure) {
1059  
1060        // The easy cases first.
1061        case 'seconds':
1062          return $diff;
1063        case 'minutes':
1064          return $diff / 60;
1065        case 'hours':
1066          return $diff / 3600;
1067        case 'years':
1068          return $year_diff;
1069  
1070        case 'months':
1071          $format = 'n';
1072          $item1 = date_format($date1, $format);
1073          $item2 = date_format($date2, $format);
1074          if ($year_diff == 0) {
1075            return intval($item2 - $item1);
1076          }
1077          else {
1078            $item_diff = 12 - $item1;
1079            $item_diff += intval(($year_diff - 1) * 12);
1080            return $item_diff + $item2;
1081         }
1082         break;
1083  
1084        case 'days':
1085          $format = 'z';
1086          $item1 = date_format($date1, $format);
1087          $item2 = date_format($date2, $format);
1088          if ($year_diff == 0) {
1089            return intval($item2 - $item1);
1090          }
1091          else {
1092            $item_diff = date_days_in_year($date1) - $item1;
1093            for ($i = 1; $i < $year_diff; $i++) {
1094              date_modify($date1, '+1 year');
1095              $item_diff += date_days_in_year($date1);
1096            }
1097            return $item_diff + $item2;
1098         }
1099         break;
1100  
1101        case 'weeks':
1102          $week_diff = date_format($date2, 'W') - date_format($date1, 'W');
1103          $year_diff = date_format($date2, 'o') - date_format($date1, 'o');
1104          for ($i = 1; $i <= $year_diff; $i++) {
1105            date_modify($date1, '+1 year');
1106            $week_diff += date_iso_weeks_in_year($date1);
1107          }
1108          return $week_diff;
1109      }
1110    }
1111    return NULL;
1112  }
1113  
1114  /**
1115   * Start and end dates for a calendar week, adjusted to use the
1116   * chosen first day of week for this site.
1117   */
1118  function date_week_range($week, $year) {
1119    if (variable_get('date_api_use_iso8601', FALSE)) {
1120      return date_iso_week_range($week, $year);
1121    }
1122  
1123    $min_date = date_make_date($year .'-01-01 00:00:00', date_default_timezone_name());
1124    date_timezone_set($min_date, date_default_timezone());
1125  
1126    // move to the right week
1127    date_modify($min_date, '+' . strval(7 * ($week - 1)) . ' days');
1128  
1129    // move backwards to the first day of the week
1130    $first_day = variable_get('date_first_day', 1);
1131    $day_wday = date_format($min_date, 'w');
1132    date_modify($min_date, '-' . strval((7 + $day_wday - $first_day) % 7) . ' days');
1133  
1134    // move forwards to the last day of the week
1135    $max_date = drupal_clone($min_date);
1136    date_modify($max_date, '+7 days');
1137  
1138    if (date_format($min_date, 'Y') != $year) {
1139      $min_date = date_make_date($year .'-01-01 00:00:00', date_default_timezone());
1140    }
1141    return array($min_date, $max_date);
1142  }
1143  
1144  /**
1145   * Start and end dates for an ISO week.
1146   */
1147  function date_iso_week_range($week, $year) {
1148  
1149    // Get to the last ISO week of the previous year.
1150    $min_date = date_make_date(($year - 1) .'-12-28 00:00:00', date_default_timezone_name());
1151    date_timezone_set($min_date, date_default_timezone());
1152  
1153    // Find the first day of the first ISO week in the year.
1154    date_modify($min_date, '+1 Monday');
1155  
1156    // Jump ahead to the desired week for the beginning of the week range.
1157    if ($week > 1) {
1158      date_modify($min_date, '+ '. ($week - 1) .' weeks');
1159    }
1160  
1161    // move forwards to the last day of the week
1162    $max_date = drupal_clone($min_date);
1163    date_modify($max_date, '+7 days');
1164    return array($min_date, $max_date);
1165  }
1166  
1167  /**
1168   * The number of calendar weeks in a year.
1169   * 
1170   * PHP week functions return the ISO week, not the calendar week.
1171   *
1172   * @param int $year
1173   * @return int number of calendar weeks in selected year.
1174   */
1175  function date_weeks_in_year($year) {
1176    $date = date_make_date(($year + 1) . '-01-01 12:00:00', 'UTC');
1177    date_modify($date, '-1 day');
1178    return date_week(date_format($date, 'Y-m-d'));
1179  }
1180  
1181  /**
1182   * The calendar week number for a date.
1183   * 
1184   * PHP week functions return the ISO week, not the calendar week.
1185   *
1186   * @param string $date, in the format Y-m-d
1187   * @return int calendar week number.
1188   */
1189  function date_week($date) {
1190    $date = substr($date, 0, 10);
1191    $parts = explode('-', $date);
1192    $date = date_make_date($date . ' 12:00:00', 'UTC');
1193  
1194    // If we are using ISO weeks, this is easy.
1195    if (variable_get('date_api_use_iso8601', FALSE)) {
1196      return intval(date_format($date, 'W'));
1197    }
1198  
1199    $year_date = date_make_date($parts[0] . '-01-01 12:00:00', 'UTC');
1200    $week = intval(date_format($date, 'W'));
1201    $year_week = intval(date_format($year_date, 'W'));
1202    $date_year = intval(date_format($date, 'o'));
1203  
1204    // remove the leap week if it's present
1205    if ($date_year > intval($parts[0])) {
1206      $last_date = drupal_clone($date);
1207      date_modify($last_date, '-7 days');
1208      $week = date_format($last_date, 'W') + 1;
1209    } 
1210    else if ($date_year < intval($parts[0])) {
1211      $week = 0;
1212    }
1213  
1214    if ($year_week != 1) $week++;
1215  
1216    // convert to ISO-8601 day number, to match weeks calculated above
1217    $iso_first_day = 1 + (variable_get('date_first_day', 1) + 6) % 7;
1218  
1219    // if it's before the starting day, it's the previous week
1220    if (intval(date_format($date, 'N')) < $iso_first_day) $week--;
1221  
1222    // if the year starts before, it's an extra week at the beginning
1223    if (intval(date_format($year_date, 'N')) < $iso_first_day) $week++;
1224  
1225    return $week;
1226  }
1227  
1228  /**
1229   * Date conversion helper function.
1230   *
1231   * A variety of ways to convert dates from one type to another.
1232   * No timezone conversion is done in this operation, except
1233   * when handling timestamps because create_date() assumes
1234   * timestamps hold the UTC value for the time.
1235   *
1236   * @param mixed $date
1237   *   the date to convert
1238   * @param string $from_type
1239   *   the type of date to convert from
1240   * @param string $to_type
1241   *   the type of date to convert to
1242   * @param string $tz
1243   *   the timezone of the supplied value, only needed when using timestamps
1244   *   for dates not set to UTC.
1245   */
1246  function date_convert($date, $from_type, $to_type, $tz = 'UTC') {
1247    if (empty($date) && !$date === 0) return NULL;
1248    if (empty($from_type) || empty($to_type) || $from_type == $to_type) return $date;
1249    switch ($from_type) {
1250      case DATE_ARRAY:
1251        if (!is_array($date)) return NULL;
1252        if (isset($date['ampm'])) {
1253          if ($date['ampm'] == 'pm' && $date['hour'] < 12) $date['hour'] += 12;
1254          if ($date['ampm'] == 'am' && $date['hour'] == 12) $date['hour'] -= 12;
1255        }
1256        $datetime = date_pad(intval($date['year']), 4) .'-'. date_pad(intval($date['month'])) .
1257              '-'. date_pad(intval($date['day'])) .' '. date_pad(intval($date['hour'])) .
1258              ':'. date_pad(intval($date['minute'])) .':'. date_pad(intval($date['second']));
1259        switch ($to_type) {
1260          case DATE_ISO:
1261            return str_replace(' ', 'T', $datetime);
1262          case DATE_DATETIME:
1263            return $datetime;
1264          case DATE_ICAL:
1265            $replace = array(' ' => 'T', '-' => '', ':' => '');
1266            return strtr($datetime, $replace);
1267          case DATE_OBJECT:    
1268            return date_create($datetime, timezone_open($tz));
1269          case DATE_UNIX:
1270            $obj = date_create($datetime, timezone_open($tz));
1271            return date_format($obj, 'U');  
1272        }
1273        break;
1274      case DATE_OBJECT:
1275        if (!is_object($date)) return NULL;
1276        $obj = $date;
1277        break;
1278      case DATE_DATETIME:
1279      case DATE_ISO:
1280        if (!preg_match(DATE_REGEX_LOOSE, $date)) return NULL;
1281        $date = date_fuzzy_datetime($date);
1282        $obj = date_create($date, timezone_open($tz));
1283        break;
1284      case DATE_ICAL:
1285        if (!preg_match(DATE_REGEX_LOOSE, $date)) return NULL;
1286        preg_match(DATE_REGEX_LOOSE, $date, $regs);
1287        $datetime = date_pad($regs[1], 4) .'-'. date_pad($regs[2]) .'-'. date_pad($regs[3]) .
1288          'T'. date_pad($regs[5]) .':'. date_pad($regs[6]) .':'. date_pad($regs[7]);
1289        $obj = date_create($datetime, timezone_open($tz));
1290        break;
1291      case DATE_UNIX:
1292        if (!is_numeric($date)) return NULL;
1293        // Special case when creating dates with timestamps.
1294        // The date_create() function will assume date is UTC value
1295        // and will ignore our timezone.
1296        $obj = date_create("@$date", timezone_open('UTC'));
1297        date_timezone_set($obj, timezone_open($tz));
1298        break;
1299    }
1300    switch ($to_type) {
1301      case DATE_OBJECT:
1302        return $obj;
1303      case DATE_DATETIME:
1304        return date_format($obj, DATE_FORMAT_DATETIME);
1305      case DATE_ISO:
1306        return date_format($obj, DATE_FORMAT_ISO);
1307      case DATE_ICAL:
1308        return date_format($obj, DATE_FORMAT_ICAL);
1309      case DATE_UNIX:
1310        return date_format($obj, 'U');
1311      case DATE_ARRAY:
1312        $date_array = date_array($obj);
1313        // ISO dates may contain zero values for some date parts,
1314        // make sure they don't get lost in the conversion.
1315        if ($from_type == DATE_ISO) {
1316          $date_array = array_merge($date_array, date_iso_array($date));
1317        }
1318        return $date_array;
1319      default:
1320        return NULL;
1321    }
1322  }
1323  
1324  /**
1325   * Create valid datetime value from incomplete ISO dates or arrays.
1326   */
1327  function date_fuzzy_datetime($date) {
1328    // A text ISO date, like MMMM-YY-DD HH:MM:SS
1329    if (!is_array($date)) {
1330      $date = date_iso_array($date);
1331    }
1332    // An date/time value in the format:
1333    //  array('date' => MMMM-YY-DD, 'time' => HH:MM:SS).
1334    elseif (array_key_exists('date', $date) || array_key_exists('time', $date)) {
1335      $date_part = array_key_exists('date', $date) ? $date['date'] : '';
1336      $time_part = array_key_exists('time', $date) ? $date['time'] : '';
1337      $date = date_iso_array(trim($date_part .' '. $time_part));
1338    }
1339    // Otherwise date must in in format:
1340    //  array('year' => YYYY, 'month' => MM, 'day' => DD).
1341    if (empty($date['year'])) {
1342      $date['year'] = date('Y');
1343    }
1344    if (empty($date['month'])) {
1345      $date['month'] = 1;
1346    }
1347    if (empty($date['day'])) {
1348      $date['day'] = 1;
1349    }
1350    foreach(array('hour', 'minute', 'second') as $part) {
1351      if (empty($date[$part])) {
1352        $date[$part] = 0;
1353      }
1354    }
1355    $value = date_pad($date['year'], 4) .'-'. date_pad($date['month']) .'-'. 
1356      date_pad($date['day']) .' '. date_pad($date['hour']) .':'. 
1357      date_pad($date['minute']) .':'. date_pad($date['second']);
1358    return $value;
1359  }
1360  
1361  /**
1362   * Create an array of date parts from an ISO date.
1363   */
1364  function date_iso_array($date) {
1365    preg_match(DATE_REGEX_LOOSE, $date, $regs);
1366    return array(
1367      'year' => isset($regs[1]) ? intval($regs[1]) : '',
1368      'month' => isset($regs[2]) ? intval($regs[2]) : '',
1369      'day' => isset($regs[3]) ? intval($regs[3]) : '',
1370      'hour' => isset($regs[5]) ? intval($regs[5]) : '',
1371      'minute' => isset($regs[6]) ? intval($regs[6]) : '',
1372      'second' => isset($regs[7]) ? intval($regs[7]) : '',
1373      );       
1374  }
1375  
1376  /**
1377   * Create an array of values from a date object. Structured like the
1378   * results of getdate() but not limited to the 32-bit signed range.
1379   *
1380   * @param object $obj
1381   * @return array
1382   */
1383  function date_array($obj) {
1384    $year = intval(date_format($obj, 'Y'));
1385    $dow = date_format($obj, 'w');
1386    $days = date_week_days();
1387    return array(
1388      'second' => (integer) date_format($obj, 's'),
1389      'minute' => (integer) date_format($obj, 'i'),
1390      'hour' => date_format($obj, 'G'),
1391      'day' => date_format($obj, 'j'),
1392      'wday' => $dow,
1393      'month' => date_format($obj, 'n'),
1394      'year' => date_format($obj, 'Y'),
1395      'yday' => date_format($obj, 'z'),
1396      'weekday' => $days[$dow],
1397      'month_name' => date_format($obj, 'F'),
1398      0 => date_format($obj, 'U'));
1399  }
1400  
1401  /**
1402   * Extract integer value of any date part from any type of date.
1403   *
1404   * Example:
1405   *   date_part_extract('2007-03-15 00:00', 'month', DATE_DATETIME)
1406   *   returns: 3
1407   *
1408   * @param mixed $date
1409   *   the date value to analyze.
1410   * @param string $part
1411   *   the part of the date to extract, 'year', 'month', 'day', 'hour', 'minute', 'second'
1412   * @param string $type
1413   *   the type of date supplied, DATE_ISO, DATE_UNIX, DATE_DATETIME, or DATE_OBJECT;
1414   * @return integer
1415   *   the integer value of the requested date part.
1416   */
1417  function date_part_extract($date, $part, $type = DATE_DATETIME, $tz = 'UTC') {
1418    $formats = array('year' => 'Y', 'month' => 'n', 'day' => 'j',
1419      'hour' => 'G', 'minute' => 'i', 'second' => 's');
1420    $positions = array('year' => 0, 'month' => 5, 'day' => 8,
1421      'hour' => 11, 'minute' => 14, 'second' => 17);
1422    $ipositions = array('year' => 0, 'month' => 4, 'day' => 6,
1423      'hour' => 9, 'minute' => 11, 'second' => 13);
1424    switch ($type) {
1425      case DATE_ARRAY:
1426        return (integer) array_key_exists($part, $date) ? $date[$part] : NULL;
1427      case DATE_DATETIME:
1428      case DATE_ISO:
1429        return (integer) substr($date, $positions[$part], $part == 'year' ? 4 : 2);
1430      case DATE_ICAL:
1431        return (integer) substr($date, $ipositions[$part], $part == 'year' ? 4 : 2);
1432      case DATE_UNIX:
1433        // Special case when creating dates with timestamps.
1434        // The date_create() function will assume date is UTC value
1435        // and will ignore our timezone.
1436        $date = date_create("@$date", timezone_open('UTC'));
1437        date_timezone_set($date, timezone_open($tz));
1438        return date_format($date, $formats[$part]);
1439      case DATE_OBJECT:
1440        return date_format($date, $formats[$part]);
1441    }
1442   }
1443  
1444  /**
1445   *  Functions to test the validity of a date in various formats.
1446   *  Has special case for ISO dates and arrays which can be missing
1447   *  month and day and still be valid.
1448   *
1449   *  @param $type
1450   *    could be DATE_ARRAY, DATE_UNIX, DATE_DATETIME, DATE_ISO, or DATE_OBJECT
1451   *  @param $granularity
1452   *    The granularity of the date value provided. Set this for partial
1453   *    dates so they pass validation.
1454   */
1455  function date_is_valid($date, $type = DATE_DATETIME, $granularity = array('year', 'month', 'day', 'hour', 'minute')) {
1456  
1457    // Check that the value is properly structured.
1458    // Remember that DATE_UNIX can have a valid value of '0', which is 'empty'.
1459    if (empty($date) && $type != DATE_UNIX) return FALSE;
1460    if ($type == DATE_OBJECT && !is_object($date)) return FALSE;
1461    if (($type == DATE_ISO || $type == DATE_DATETIME) && (!is_string($date) || !preg_match(DATE_REGEX_LOOSE, $date))) return FALSE;
1462    if ($type == DATE_UNIX and !is_numeric($date)) return FALSE;
1463    if ($type == DATE_ARRAY and !is_array($date)) return FALSE;
1464  
1465    // Make sure integer values are sent to checkdate.
1466    $year = intval(date_part_extract($date, 'year', $type));
1467    $month = intval(date_part_extract($date, 'month', $type));
1468    $day = intval(date_part_extract($date, 'day', $type));
1469    if (checkdate($month, $day, $year)) {
1470      return TRUE;
1471    }
1472  
1473    // If this is an incomplete date (year only or year and month only), 
1474    // need special handling, partial dates can have empty date parts.
1475    $max_granularity = $granularity;
1476    $max_granularity = array_pop($max_granularity);
1477    if (!in_array($max_granularity, array('year', 'month'))) {
1478      if (in_array('year', $granularity) && !date_valid_year($year)) {
1479        return FALSE;
1480      }
1481      elseif (in_array('month', $granularity) && !date_valid_month($month)) {
1482        return FALSE;
1483      }
1484      elseif (in_array('day', $granularity) && !date_valid_day($day, $month, $year)) {
1485        return FALSE;
1486      }
1487    }
1488    // ISO dates and arrays can have empty date parts.
1489    elseif ($type == DATE_ISO || $type == DATE_ARRAY) {
1490      if (!date_valid_year($year)) {
1491        return FALSE;
1492      }
1493      elseif (!empty($month) && !date_valid_month($month)) {
1494        return FALSE;
1495      }
1496      elseif (!empty($day) && !date_valid_day($day, $month, $year)) {
1497        return FALSE;
1498      }
1499    }
1500    elseif(!date_valid_year($year) || !date_valid_month($month) || date_valid_day($day, $month, $year)) {
1501      // Unix and datetime are expected to have at least a year, month, and day.
1502      return FALSE;
1503    }
1504  
1505    return TRUE;
1506  }
1507  
1508  function date_valid_year($year) {
1509    if (variable_get('date_max_year', 4000) < $year || variable_get('date_min_year', 1) > $year) {
1510      return FALSE;
1511    }
1512    else {
1513      return TRUE;
1514    }      
1515  }
1516  
1517  function date_valid_month($month) {
1518    if (12 < $month || 0 > $month) {
1519      return FALSE;
1520    }
1521    else {
1522      return TRUE;
1523    }
1524  }
1525  
1526  function date_valid_day($day, $month = NULL, $year = NULL) {
1527    $days_in_month = !empty($month) && !empty($year) ? date_days_in_month($year, $month) : 31;
1528    if ($days_in_month < $day || 1 > $day) {
1529      return FALSE;
1530    }
1531    else {
1532      return TRUE;
1533    }
1534  }
1535  
1536  /**
1537   * Helper function to left pad date parts with zeros.
1538   * Provided because this is needed so often with dates.
1539   *
1540   * @param int $value
1541   *   the value to pad
1542   * @param int $size
1543   *   total size expected, usually 2 or 4
1544   * @return string the padded value
1545   */
1546  function date_pad($value, $size = 2) {
1547    return sprintf("%0". $size ."d", $value);
1548  }
1549  
1550  /**
1551   *  Function to figure out if any time data is to be collected or displayed.
1552   *
1553   *  @param granularity
1554   *    an array like ('year', 'month', 'day', 'hour', 'minute', 'second');
1555   */
1556  function date_has_time($granularity) {
1557    if (!is_array($granularity)) $granularity = array();
1558    return sizeof(array_intersect($granularity, array('hour', 'minute', 'second'))) > 0 ? TRUE : FALSE;
1559  }
1560  
1561  function date_has_date($granularity) {
1562    if (!is_array($granularity)) $granularity = array();
1563    return sizeof(array_intersect($granularity, array('year', 'month', 'day'))) > 0 ? TRUE : FALSE;
1564  }
1565  /**
1566   * Recalculate a date so it only includes elements from a granularity
1567   * array. Helps prevent errors when unwanted values round up and ensures
1568   * that unwanted date part values don't get stored in the database.
1569   *
1570   * Example:
1571   *   date_limit_value('2007-05-15 04:45:59', array('year', 'month', 'day'))
1572   *   returns '2007-05-15 00:00:00'
1573   *
1574   * @param $date
1575   *   a date value
1576   * @param $granularity
1577   *   an array of allowed date parts, like ('year', 'month', 'day', 'hour', 'minute', 'second');
1578   * @param $type
1579   *   the type of date value provided,
1580   *   DATE_DATETIME, DATE_ISO, DATE_UNIX, or DATE_ARRAY
1581   * @return
1582   *   the date with the unwanted parts reset to zeros (or ones if zeros are
1583   *   invalid for that date type).
1584  */
1585  function date_limit_value($date, $granularity, $type = DATE_DATETIME) {
1586    if (!date_is_valid($date, $type, $granularity) || !$nongranularity = date_nongranularity($granularity)) {
1587     return $date;
1588    }
1589    else {
1590      $date = date_convert($date, $type, DATE_ARRAY);
1591      foreach ($nongranularity as $level) {
1592        switch ($level) {
1593          case 'second':
1594            $date['second'] = 0;
1595            break;
1596          case 'minute':
1597            $date['minute'] = 0;
1598            break;
1599          case 'hour':
1600            $date['hour'] = 0;
1601            break;
1602          case 'month':
1603            $date['month'] = $type != DATE_ISO ? 1 : 0;
1604            break;
1605          case 'day':
1606            $date['day'] = $type != DATE_ISO ? 1 : 0;
1607            break;
1608         }
1609      }
1610      return date_convert($date, DATE_ARRAY, $type);
1611    }
1612  }
1613  
1614  /**
1615   * Rewrite a format string so it only includes elements from a
1616   * specified granularity array.
1617   *
1618   * Example:
1619   *   date_limit_format('F j, Y - H:i', array('year', 'month', 'day'));
1620   *   returns 'F j, Y'
1621   *
1622   * @param $format
1623   *   a format string
1624   * @param $granularity
1625   *   an array of allowed date parts, all others will be removed
1626   *   array('year', 'month', 'day', 'hour', 'minute', 'second');
1627   * @return
1628   *   a format string with all other elements removed
1629   */
1630  function date_limit_format($format, $granularity) {
1631    // If punctuation has been escaped, remove the escaping.
1632    // Done using strtr because it is easier than getting the
1633    // escape character extracted using preg_replace.
1634    $replace = array(
1635      '\-' => '-',
1636      '\:' => ':',
1637      "\'" => "'",
1638      '\.' => '.',
1639      '\,' => ',',
1640      );
1641    $format = strtr($format, $replace); 
1642  
1643    // Get the 'T' out of ISO date formats that don't have
1644    // both date and time.
1645    if (!date_has_time($granularity) || !date_has_date($granularity)) {
1646      $format = str_replace('\T', ' ', $format);
1647      $format = str_replace('T', ' ', $format);
1648    }
1649  
1650    $regex = array();
1651    if (!date_has_time($granularity)) {
1652      $regex[] = '((?<!\\\\)[a|A])';
1653    }
1654    // Create regular expressions to remove selected values from string.
1655    // Use (?<!\\\\) to keep escaped letters from being removed.
1656    foreach (date_nongranularity($granularity) as $element) {
1657      switch ($element) {
1658        case 'year':
1659          $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[Yy])';
1660          break;
1661        case 'day':
1662          $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[l|D|d|dS|j|jS|N|w|W|z]{1,2})';
1663          break;
1664        case 'month':
1665          $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[FMmn])';
1666          break;
1667        case 'hour':
1668          $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[HhGg])';
1669          break;
1670        case 'minute':
1671          $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[i])';
1672          break;
1673        case 'second':
1674          $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[s])';
1675          break;
1676        case 'timezone':
1677          $regex[] = '([\-/\.,:]?\s?(?<!\\\\)[TOZPe])';
1678          break;
1679  
1680      }
1681    }
1682    // Remove empty parentheses, brackets, pipes.
1683    $regex[] = '(\(\))';
1684    $regex[] = '(\[\])';
1685    $regex[] = '(\|\|)';
1686  
1687    // Remove selected values from string.
1688    $format = trim(preg_replace($regex, array(), $format));
1689    // Remove orphaned punctuation at the beginning of the string.
1690    $format = preg_replace('`^([\-/\.,:\'])`', '', $format);
1691    // Remove orphaned punctuation at the end of the string.
1692    $format = preg_replace('([\-/\.,:\']$)', '', $format);
1693    $format = preg_replace('(\\$)', '', $format);
1694  
1695    // Trim any whitespace from the result.
1696    $format = trim($format);
1697  
1698    // After removing the non-desired parts of the format, test if the only 
1699    // things left are escaped, non-date, characters. If so, return nothing.
1700    if (!$test = trim(preg_replace('(\\\\\w{1})', '', $format))) {
1701      return '';
1702    }
1703    return $format;
1704  }
1705  
1706  /**
1707   * Convert a format to an ordered array of granularity parts.
1708   *
1709   * Example:
1710   *   date_format_order('m/d/Y H:i')
1711   *   returns
1712   *     array(
1713   *       0 => 'month',
1714   *       1 => 'day',
1715   *       2 => 'year',
1716   *       3 => 'hour',
1717   *       4 => 'minute',
1718   *     );
1719   *
1720   * @param string $format
1721   * @return array of ordered granularity elements in this format string
1722   */
1723  function date_format_order($format) {
1724    $order = array();
1725    if (empty($format)) return $order;
1726    $max = strlen($format);
1727    for ($i = 0; $i <= $max; $i++) {
1728      if (!isset($format[$i])) break;
1729      $c = $format[$i];
1730      switch ($c) {
1731        case 'd':
1732        case 'j':
1733          $order[] = 'day';
1734          break;
1735        case 'F':
1736        case 'M':
1737        case 'm':
1738        case 'n':
1739          $order[] = 'month';
1740          break;
1741        case 'Y':
1742        case 'y':
1743          $order[] = 'year';
1744          break;
1745        case 'g':
1746        case 'G':
1747        case 'h':
1748        case 'H':
1749          $order[] = 'hour';
1750          break;
1751        case 'i':
1752          $order[] = 'minute';
1753          break;
1754        case 's':
1755          $order[] = 'second';
1756          break;
1757      }
1758    }
1759    return $order;
1760  }
1761  
1762  /**
1763   * An difference array of granularity elements that are NOT in the
1764   * granularity array. Used by functions that strip unwanted
1765   * granularity elements out of formats and values.
1766   *
1767   * @param $granularity
1768   *   an array like ('year', 'month', 'day', 'hour', 'minute', 'second');
1769   */
1770  function date_nongranularity($granularity) {
1771    return array_diff(array('year', 'month', 'day', 'hour', 'minute', 'second', 'timezone'), (array) $granularity);
1772  }
1773  
1774  /**
1775   * Implementation of hook_simpletest().
1776   */
1777  function date_api_simpletest() {
1778    $dir = drupal_get_path('module', 'date_api') .'/tests';
1779    $tests = file_scan_directory($dir, '\.test$');
1780    return array_keys($tests);
1781  }
1782  
1783  /**
1784   * Implementation of hook_elements().
1785   */
1786  function date_api_elements() {
1787    require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_elements.inc');
1788    return _date_api_elements();
1789  }
1790  
1791  function date_api_theme() {
1792    $path = drupal_get_path('module', 'date_api');
1793    $base = array(
1794      'file' => 'theme.inc',
1795      'path' => "$path/theme",
1796    );
1797    return array(
1798      'date_nav_title' => $base + array('arguments' => array('type' => NULL, 'view' => NULL)),
1799      'date_vcalendar' => $base + array('arguments' => array('events' => NULL, 'calname' => NULL)),
1800      'date_vevent' => $base + array('arguments' => array('event' => NULL)),
1801      'date_valarm' => $base + array('arguments' => array('alarm' => NULL)),
1802      'date_timezone' => $base + array('arguments' => array('element' => NULL)),
1803      'date_select' => $base + array('arguments' => array('element' => NULL)),
1804      'date_text' => $base + array('arguments' => array('element' => NULL)),
1805      'date_select_element' => $base + array('arguments' => array('element' => NULL)),
1806      'date_textfield_element' => $base + array('arguments' => array('element' => NULL)),
1807      'date_date_part_hour_prefix' => $base + array('arguments' => array('element' => NULL)),
1808      'date_part_minsec_prefix' => $base + array('arguments' => array('element' => NULL)),
1809      'date_part_label_year' => $base + array('arguments' => array('element' => NULL)),
1810      'date_part_label_month' => $base + array('arguments' => array('element' => NULL)),
1811      'date_part_label_day' => $base + array('arguments' => array('element' => NULL)),
1812      'date_part_label_hour' => $base + array('arguments' => array('element' => NULL)),
1813      'date_part_label_minute' => $base + array('arguments' => array('element' => NULL)),
1814      'date_part_label_second' => $base + array('arguments' => array('element' => NULL)),
1815      'date_part_label_ampm' => $base + array('arguments' => array('element' => NULL)),
1816      'date_part_label_timezone' => $base + array('arguments' => array('element' => NULL)),
1817      'date_views_filter_form' => $base + array(
1818        'template' => 'date-views-filter-form',
1819        'arguments' => array('form' => NULL)),
1820      'date_calendar_day' => $base + array('arguments' => array('date' => NULL)),  
1821      'date_time_ago' => $base + array('arguments' => array('start_date' => NULL, 'end_date' => NULL, 'interval' => NULL)));
1822  }
1823  
1824  /**
1825   * Wrapper around date handler setting for timezone.
1826   */
1827  function date_api_set_db_timezone($offset = '+00:00') {
1828    require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_sql.inc');
1829    $handler = new date_sql_handler();
1830    return $handler->set_db_timezone($offset);
1831  }
1832  
1833  /**
1834   *  Function to figure out which local timezone applies to a date and select it
1835   */
1836  function date_get_timezone($handling, $timezone = '') {
1837    switch ($handling) {
1838      case ('date'):
1839        $timezone = !empty($timezone) ? $timezone : date_default_timezone_name();
1840        break;
1841      case ('utc'):
1842        $timezone = 'UTC';
1843        break;
1844      default:
1845        $timezone = date_default_timezone_name();
1846    }
1847    return $timezone > '' ? $timezone : date_default_timezone_name();
1848  }
1849  
1850  /**
1851   *  Function to figure out which db timezone applies to a date and select it
1852   */
1853  function date_get_timezone_db($handling, $timezone = '') {
1854    switch ($handling) {
1855      case ('none'):
1856        $timezone = date_default_timezone_name();
1857        break;
1858      default:
1859        $timezone = 'UTC';
1860        break;
1861    }
1862    return $timezone > '' ? $timezone : 'UTC';
1863  }
1864  
1865  /**
1866   * Wrapper function to make sure this function will always work.
1867   */
1868  function date_api_views_fetch_fields($base, $type) {
1869    if (!module_exists('views')) {
1870      return array();
1871    }
1872    require_once('./'. drupal_get_path('module', 'views') .'/includes/admin.inc');
1873    return views_fetch_fields($base, $type);
1874  }
1875  
1876  /**
1877   * Get the list of date formats for a particular format length.
1878   *
1879   * @param $type
1880   *   The format type: 'short', 'medium', 'long', 'custom'.  If empty, then all
1881   *   available formats will be returned.
1882   * @param $reset
1883   *   Whether or not to reset this function's internal cache (defaults to FALSE).
1884   * @return
1885   *   Array of date formats.
1886   */
1887  function date_get_formats($type = NULL, $reset = FALSE) {
1888    static $_date_formats;
1889  
1890    if ($reset || !isset($_date_formats)) {
1891      $_date_formats = _date_formats_build();
1892    }
1893  
1894    return $type ? (isset($_date_formats[$type]) ? $_date_formats[$type] : FALSE) : $_date_formats;
1895  }
1896  
1897  /**
1898   * Get the format details for a particular id.
1899   *
1900   * @param $dfid
1901   *   Identifier of a date format string.
1902   * @return
1903   *   Array of date format details.
1904   */
1905  function date_get_format($dfid) {
1906    $result = db_query('SELECT df.dfid, df.format, df.type, df.locked FROM {date_formats} df WHERE df.dfid = %d', $dfid);
1907    return db_fetch_array($result);
1908  }
1909  
1910  /**
1911   * Get the list of available date format types and attributes.
1912   *
1913   * @param $type
1914   *   The format type, e.g. 'short', 'medium', 'long', 'custom'.  If empty, then
1915   *   all attributes for that type will be returned.
1916   * @param $reset
1917   *   Whether or not to reset this function's internal cache (defaults to FALSE).
1918   * @return
1919   *   Array of date format types.
1920   */
1921  function date_get_format_types($type = NULL, $reset = FALSE) {
1922    static $_date_format_types;
1923  
1924    if ($reset || !isset($_date_format_types)) {
1925      $_date_format_types = _date_format_types_build();
1926    }
1927  
1928    return $type ? (isset($_date_format_types[$type]) ? $_date_format_types[$type] : FALSE) : $_date_format_types;
1929  }
1930  
1931  /**
1932   * Implementation of hook_flush_caches().
1933   */
1934  function date_api_flush_caches() {
1935    // Rebuild list of date formats.
1936    date_formats_rebuild();
1937    return array();
1938  }
1939  
1940  /**
1941   * Resets the database cache of date formats, and saves all new date formats to
1942   * the database.
1943   */
1944  function date_formats_rebuild() {
1945    $date_formats = date_get_formats(NULL, TRUE);
1946  
1947    foreach ($date_formats as $format_type => $formats) {
1948      foreach ($formats as $format => $info) {
1949        date_format_save($info);
1950      }
1951    }
1952  
1953    // Rebuild configured date formats locale list.
1954    date_format_locale(NULL, NULL, TRUE);
1955  
1956    _date_formats_build();
1957  }
1958  
1959  /**
1960   * Save a date format type to the database.
1961   *
1962   * @param $date_format_type
1963   *   An array of attributes for a date format type.
1964   */
1965  function date_format_type_save($date_format_type) {
1966    $type = array();
1967    $type['type'] = $date_format_type['type'];
1968    $type['title'] = $date_format_type['title'];
1969    $type['locked'] = $date_format_type['locked'];
1970  
1971    // Update date_format table.
1972    if (isset($date_format_type['is_new']) && !empty($date_format_type['is_new'])) {
1973      drupal_write_record('date_format_types', $type);
1974    }
1975    else {
1976      drupal_write_record('date_format_types', $type, 'type');
1977    }
1978  }
1979  
1980  /**
1981   * Delete a date format type from the database.
1982   *
1983   * @param $date_format_type
1984   *   The date format type name.
1985   */
1986  function date_format_type_delete($date_format_type) {
1987    db_query("DELETE FROM {date_formats} WHERE type = '%s'", $date_format_type);
1988    db_query("DELETE FROM {date_format_types} WHERE type = '%s'", $date_format_type);
1989    db_query("DELETE FROM {date_format_locale} WHERE type = '%s'", $date_format_type);
1990  }
1991  
1992  /**
1993   * Save a date format to the database.
1994   *
1995   * @param $date_format
1996   *   An array of attributes for a date format.
1997   */
1998  function date_format_save($date_format) {
1999    $format = array();
2000    $format['type'] = $date_format['type'];
2001    $format['format'] = $date_format['format'];
2002    $format['locked'] = $date_format['locked'];
2003  
2004    // Update date_format table.
2005    if (isset($date_format['is_new']) && !empty($date_format['is_new'])) {
2006      drupal_write_record('date_formats', $format);
2007    }
2008    else {
2009      drupal_write_record('date_formats', $format, array('format', 'type'));
2010    }
2011  
2012    $languages = language_list('enabled');
2013    $languages = $languages[1];
2014    // If site_country module is enabled, add country specific languages to
2015    // languages array.
2016    if (module_exists('site_country')) {
2017      $country_code = variable_get('site_country_default_country', '');
2018      if (!empty($country_code)) {
2019        foreach ($languages as $langcode => $details) {
2020          $country_language = $langcode . '-' . $country_code;
2021          if (drupal_strlen($langcode) == 2 && !in_array($country_language, array_keys($languages))) {
2022            $name = $details->name;
2023            $languages[$country_language] = "$name ($country_code)";
2024          }
2025        }
2026      }
2027    }
2028  
2029    $locale_format = array();
2030    $locale_format['type'] = $date_format['type'];
2031    $locale_format['format'] = $date_format['format'];
2032  
2033    // Check if the suggested language codes are configured and enabled.
2034    if (!empty($date_format['locales'])) {
2035      foreach ($date_format['locales'] as $langcode) {
2036        // Only proceed if language is enabled.
2037        if (in_array($langcode, $languages)) {
2038          $is_existing = db_result(db_query("SELECT COUNT(*) FROM {date_format_locale} WHERE type = '%s' AND language = '%s'", $date_format['type'], $langcode));
2039          if (!$is_existing) {
2040            $locale_format['language'] = $langcode;
2041            drupal_write_record('date_format_locale', $locale_format);
2042          }
2043        }
2044      }
2045    }
2046  }
2047  
2048  /**
2049   * Delete a date format from the database.
2050   *
2051   * @param $date_format_id
2052   *   The date format string identifier.
2053   */
2054  function date_format_delete($date_format_id) {
2055    db_query("DELETE FROM {date_formats} WHERE dfid = '%d'", $date_format_id);
2056  }
2057  
2058  /**
2059   * Builds and returns the list of available date format types.
2060   *
2061   * @return
2062   *   Array of date format types.
2063   */
2064  function _date_format_types_build() {
2065    $types = array();
2066  
2067    // Prevent errors in the upgrade before the date_format_types table exists.
2068    if (defined('MAINTENANCE_MODE') && !db_table_exists('date_format_types')) {
2069      return $types;
2070    }
2071  
2072    // Get list of modules which implement hook_date_format_types().
2073    $modules = module_implements('date_format_types');
2074  
2075    foreach ($modules as $module) {
2076      $module_types = module_invoke($module, 'date_format_types');
2077      foreach ($module_types as $module_type => $type_title) {
2078        $type = array();
2079        $type['module'] = $module;
2080        $type['type'] = $module_type;
2081        $type['title'] = $type_title;
2082        $type['locked'] = 1;
2083        $type['is_new'] = TRUE; // Will be over-ridden later if in the db.
2084        $types[$module_type] = $type;
2085      }
2086    }
2087  
2088    // Get custom formats added to the database by the end user.
2089    $result = db_query('SELECT dft.type, dft.title, dft.locked FROM {date_format_types} dft ORDER BY dft.title');
2090    while ($object = db_fetch_object($result)) {
2091      if (!in_array($object->type, $types)) {
2092        $type = array();
2093        $type['is_new'] = FALSE;
2094        $type['module'] = '';
2095        $type['type'] = $object->type;
2096        $type['title'] = $object->title;
2097        $type['locked'] = $object->locked;
2098        $types[$object->type] = $type;
2099      }
2100      else {
2101          $type = array();
2102          $type['is_new'] = FALSE;  // Over-riding previous setting.
2103          $types[$object->type] = array_merge($types[$object->type], $type);
2104      }
2105    }
2106  
2107    // Allow other modules to modify these format types.
2108    drupal_alter('date_format_types', $types);
2109  
2110    return $types;
2111  }
2112  
2113  /**
2114   * Builds and returns the list of available date formats.
2115   *
2116   * @return
2117   *   Array of date formats.
2118   */
2119  function _date_formats_build() {
2120    $date_formats = array();
2121  
2122    // Prevent errors in the upgrade before the date_format table exists.
2123    if (defined('MAINTENANCE_MODE') && !db_table_exists('date_format')) {
2124      return $date_formats;
2125    }
2126  
2127    // First handle hook_date_format_types().
2128    $types = _date_format_types_build();
2129    foreach ($types as $type => $info) {
2130      date_format_type_save($info);
2131    }
2132  
2133    // Get formats supplied by various contrib modules.
2134    $module_formats = module_invoke_all('date_formats');
2135  
2136    foreach ($module_formats as $module_format) {
2137      $module_format['locked'] = 1; // System types are locked.
2138      // If no format type is specified, assign 'custom'.
2139      if (!isset($module_format['type'])) {
2140        $module_format['type'] = 'custom';
2141      }
2142      if (!in_array($module_format['type'], array_keys($types))) {
2143        continue;
2144      }
2145      if (!isset($date_formats[$module_format['type']])) {
2146        $date_formats[$module_format['type']] = array();
2147      }
2148  
2149      // If another module already set this format, merge in the new settings.
2150      if (isset($date_formats[$module_format['type']][$module_format['format']])) {
2151        $date_formats[$module_format['type']][$module_format['format']] = array_merge_recursive($date_formats[$module_format['type']][$module_format['format']], $format);
2152      }
2153      else {
2154        // This setting will be overridden later if it already exists in the db.
2155        $module_format['is_new'] = TRUE;
2156        $date_formats[$module_format['type']][$module_format['format']] = $module_format;
2157      }
2158    }
2159  
2160    // Get custom formats added to the database by the end user.
2161    $result = db_query('SELECT df.dfid, df.format, df.type, df.locked, dfl.language FROM {date_formats} df LEFT JOIN {date_format_types} dft ON df.type = dft.type LEFT JOIN {date_format_locale} dfl ON df.format = dfl.format AND df.type = dfl.type ORDER BY df.type, df.format');
2162    while ($object = db_fetch_object($result)) {
2163      // If this format type isn't set, initialise the array.
2164      if (!isset($date_formats[$object->type])) {
2165        $date_formats[$object->type] = array();
2166      }
2167      // If this format not already present, add it to the array.
2168      if (!isset($date_formats[$object->type][$object->format])) {
2169        // We don't set 'is_new' as it is already in the db.
2170        $format = array();
2171        $format['module'] = '';
2172        $format['dfid'] = $object->dfid;
2173        $format['format'] = $object->format;
2174        $format['type'] = $object->type;
2175        $format['locked'] = $object->locked;
2176        $format['locales'] = array($object->language);
2177        $date_formats[$object->type][$object->format] = $format;
2178      }
2179      // Format already present, so merge in settings.
2180      else {
2181        $format = array();
2182        $format['is_new'] = FALSE; // It's in the db, so override this setting.
2183        $format['dfid'] = $object->dfid;
2184        $format['format'] = $object->format;
2185        $format['type'] = $object->type;
2186        $format['locked'] = $object->locked;
2187        if (!empty($object->language)) {
2188          $format['locales'] = array_merge($date_formats[$object->type][$object->format]['locales'], array($object->language));
2189        }
2190        $date_formats[$object->type][$object->format] = array_merge($date_formats[$object->type][$object->format], $format);
2191      }
2192    }
2193  
2194    // Allow other modules to modify these formats.
2195    drupal_alter('date_formats', $date_formats);
2196  
2197    return $date_formats;
2198  }
2199  
2200  /**
2201   * Get the appropriate date format for a type and locale.
2202   *
2203   * @param $langcode
2204   *   Language code for the current locale.  This can be a 2 character language
2205   *   code like 'en', 'fr', or a longer 5 character code like 'en-gb'.
2206   * @param $type
2207   *   Date format type: short, medium, long, custom.
2208   * @param $reset
2209   *   Whether or not to reset this function's internal cache (defaults to FALSE).
2210   * @return
2211   *   The format string, or NULL if no matching format found.
2212   */
2213  function date_format_locale($langcode = NULL, $type = NULL, $reset = FALSE) {
2214    static $formats;
2215  
2216    if ($reset || empty($formats)) {
2217      $formats = array();
2218      $result = db_query("SELECT format, type, language FROM {date_format_locale}");
2219      while ($object = db_fetch_object($result)) {
2220        if (!isset($formats[$object->language])) {
2221          $formats[$object->language] = array();
2222        }
2223        $formats[$object->language][$object->type] = $object->format;
2224      }
2225    }
2226  
2227    if ($type && $langcode && !empty($formats[$langcode][$type])) {
2228      return $formats[$langcode][$type];
2229    }
2230    elseif ($langcode && !empty($formats[$langcode])) {
2231      return $formats[$langcode];
2232    }
2233  
2234    return FALSE;
2235  }
2236  
2237  /**
2238   * Helper function for BYDAY options in Date Repeat
2239   * and for converting back and forth from '+1' to 'First'.
2240   */
2241  function date_order_translated() {
2242    return array(
2243      '+1' => date_t('First', 'date_order'),
2244      '+2' => date_t('Second', 'date_order'),
2245      '+3' => date_t('Third', 'date_order'),
2246      '+4' => date_t('Fourth', 'date_order'),
2247      '+5' => date_t('Fifth', 'date_order'),
2248      '-1' => date_t('Last', 'date_order_reverse'),
2249      '-2' => date_t('Next to last', 'date_order_reverse'),
2250      '-3' => date_t('Third from last', 'date_order_reverse'),
2251      '-4' => date_t('Fourth from last', 'date_order_reverse'),
2252      '-5' => date_t('Fifth from last', 'date_order_reverse')
2253    );
2254  }
2255  
2256  function date_order() {
2257    return array(
2258      '+1' => 'First',
2259      '+2' => 'Second',
2260      '+3' => 'Third',
2261      '+4' => 'Fourth',
2262      '+5' => 'Fifth',
2263      '-1' => 'Last',
2264      '-2' => '-2',
2265      '-3' => '-3',
2266      '-4' => '-4',
2267      '-5' => '-5'
2268    );
2269  }
2270  
2271  /**
2272   * Implementation of hook_views_api().
2273   *
2274   * This one is used as the base to reduce errors when updating.
2275   */
2276  function date_api_views_api() {
2277    return array(
2278      'api' => 2,
2279      'path' => drupal_get_path('module', 'date_api') .'/includes',
2280    );
2281  }
2282  
2283  /**
2284   * Implementation of hook_date_api_fields().
2285   * on behalf of core fields.
2286   * 
2287   * All modules that create custom fields that use the 
2288   * 'views_handler_field_date' handler can provide
2289   * additional information here about the type of
2290   * date they create so the date can be used by
2291   * the Date API views date argument and date filter.
2292   */
2293  function date_api_date_api_fields($field) {
2294    $values = array(
2295      // The type of date: DATE_UNIX, DATE_ISO, DATE_DATETIME.
2296      'sql_type' => DATE_UNIX, 
2297      // Timezone handling options: 'none', 'site', 'date', 'utc'.
2298      'tz_handling' => 'site',
2299      // Needed only for dates that use 'date' tz_handling.
2300      'timezone_field' => '', 
2301      // Needed only for dates that use 'date' tz_handling.
2302      'offset_field' => '', 
2303      // Array of "table.field" values for related fields that should be 
2304      // loaded automatically in the Views SQL.
2305      'related_fields' => array(),
2306      // Granularity of this date field's db data. 
2307      'granularity' => array('year', 'month', 'day', 'hour', 'minute', 'second'),
2308    );
2309  
2310    switch ($field) {
2311      case 'users.created':
2312      case 'users.access':
2313      case 'users.login':
2314      case 'node.created':
2315      case 'node.changed':
2316      case 'node_revisions.timestamp':
2317      case 'files.timestamp':
2318      case 'node_counter.timestamp':
2319      case 'accesslog.timestamp':
2320      case 'comments.timestamp':
2321      case 'node_comment_statistics.last_comment_timestamp':  
2322        return $values;
2323    }
2324  }
2325  
2326  /**
2327   * Rebuild the theme registry and all the caches.
2328   * needed to pick up changes created by updated Views API
2329   * and other changes to Views definitions.
2330   */
2331  function date_api_views_clear() {
2332    if (db_table_exists('cache_content')) {
2333      db_query('DELETE FROM {cache_content}');
2334    }
2335    if (db_table_exists('cache_views')) {
2336      db_query('DELETE FROM {cache_views}');
2337    }
2338    if (db_table_exists('views_object_cache')) {
2339      db_query('DELETE FROM {views_object_cache}');
2340    }    
2341    db_query("DELETE FROM {cache} where cid LIKE 'theme_registry%'");
2342  }
2343  
2344  /**
2345   * Embed a view using a PHP snippet.
2346   *
2347   * This function is meant to be called from PHP snippets, should one wish to
2348   * embed a view in a node or something. It's meant to provide the simplest
2349   * solution and doesn't really offer a lot of options, but breaking the function
2350   * apart is pretty easy, and this provides a worthwhile guide to doing so.
2351   *
2352   * Note that this function does NOT display the title of the view. If you want
2353   * to do that, you will need to do what this function does manually, by
2354   * loading the view, getting the preview and then getting $view->get_title().
2355   *
2356   * @param $name
2357   *   The name of the view to embed.
2358   * 
2359   * @param $display_id
2360   *   'calendar_1' will display the calendar page, 
2361   *   'calendar_block_1' will display the calendar block.
2362   * 
2363   * @param $settings
2364   *   an array of view settings to use to override view default values;
2365   * 
2366   *   Include a setting for 'block_identifier, the identifier to use 
2367   *   for this embedded view. All embedded views that use the same 
2368   *   identifier will move together, or provide different identifiers 
2369   *   to keep them independent. The identifier will be used in the url 
2370   *   as a querystring, like: node/27?mini=calendar/2008-10.
2371   * 
2372   * @param ...
2373   *   Any additional parameters will be passed as arguments.
2374   */
2375  function date_embed_view($name, $display_id = 'default', $settings = array(), $args = array()) {
2376    $view = views_get_view($name);
2377    if (!$view) {
2378      return;
2379    }
2380    if (!empty($settings)) {
2381      foreach ($settings as $key => $setting) {
2382        $view->$key = $setting;
2383      }
2384    }
2385    if (!isset($view->date_info->block_identifier)) {
2386      $view->date_info->block_identifier = 'mini';
2387    }
2388    return $view->preview($display_id, $args);
2389  }
2390  
2391  /**
2392   * Figure out the URL of the date view we're currently looking at,
2393   * adapted to various date types or specific date arguments.
2394   * 
2395   * @param $date_type
2396   *  - if not empty, return the url of a specific date type.
2397   * @param $date_arg
2398   *  - if not empty, return the url for a view with a specific date argument.
2399   * @param $force_view_url
2400   *  - always use the view url, even if embedded.
2401   * @return 
2402   *   return the requested view url.
2403   */
2404  function date_real_url($view, $date_type = NULL, $date_arg = NULL, $force_view_url = FALSE) {
2405    $args = $view->args;
2406    $pos = $view->date_info->date_arg_pos;
2407  
2408    // The View arguments array is indexed numerically but is not necessarily
2409    // in numerical order. Sort the arguments to ensure the correct order.
2410    ksort($args);
2411  
2412    // If there are empty arguments before the date argument, 
2413    // pad them with the wildcard so the date argument will be in 
2414    // the right position.
2415    if (count($args) < $pos) {
2416      foreach ($view->argument as $name => $argument) {
2417        if ($argument->position == $pos) {
2418          break;
2419        }
2420        $args[] = $argument->options['wildcard'];
2421      }
2422    }
2423  
2424    if (!empty($date_type)) {
2425      switch ($date_type) {
2426        case 'year':
2427          $args[$pos] = date_pad($view->date_info->year, 4);
2428          break;
2429        case 'week':
2430          $args[$pos] = date_pad($view->date_info->year, 4) .'-W'. date_pad($view->date_info->week);
2431          break;
2432        case 'day':
2433          $args[$pos] = date_pad($view->date_info->year, 4) .'-'. date_pad($view->date_info->month) .'-'. date_pad($view->date_info->day);
2434          break;
2435        default:
2436          $args[$pos] = date_pad($view->date_info->year, 4) .'-'. date_pad($view->date_info->month);
2437          break;
2438      }
2439    }
2440    elseif (!empty($date_arg)) {
2441      $args[$pos] = $date_arg;
2442    }
2443    else {
2444      $args = $view->args;
2445    }
2446    // Is this an embedded or a block view?
2447    if (!$force_view_url && 
2448    (!empty($view->preview) || !empty($view->date_info->block_identifier))) {
2449      $url = $view->get_url($args);
2450      $key = date_block_identifier($view);
2451      if (!empty($key)) {
2452        return url($_GET['q'], array(
2453          'query' => date_querystring($view, array($key => $url)), 
2454          'absolute' => TRUE));
2455      }
2456    }  
2457    // Normal views may need querystrings appended to them
2458    // if they use exposed filters.
2459    return url($view->get_url($args), array(
2460      'query' => date_querystring($view), 
2461      'absolute' => TRUE));
2462  }
2463  
2464  /**
2465   * Pick up filter and sort info from url.
2466   */
2467  function date_querystring($view, $extra_params = array()) {
2468    $query_params = array_merge($_GET, $extra_params);
2469    // Allow NULL params to be removed from the query string.
2470    foreach ($extra_params AS $key => $value) {
2471      if (!isset($value)) {
2472        unset($query_params[$key]);
2473      }
2474    }
2475    // Filter the special "q" and "view" variables out of the query string.
2476    $exclude = array('q');
2477    $query = drupal_query_string_encode($query_params, $exclude);
2478    // To prevent an empty query string from adding a "?" on to the end of a URL,
2479    // we return NULL.
2480    return !empty($query) ? $query : NULL;
2481  }
2482  
2483  function date_block_identifier($view) {
2484    if (!empty($view->block_identifier)) {
2485      return $view->block_identifier;
2486    }
2487    return isset($view->date_info->block_identifier) ? $view->date_info->block_identifier : NULL;
2488  }
2489  
2490  /**
2491   * Implementation of hook_form_alter().
2492   *
2493   * Add new submit handler for system_modules form.
2494   */
2495  function date_api_form_system_modules_alter(&$form, $form_state, $form_id = 'system_modules') {
2496    $form['#submit'][] = 'date_api_system_modules_submit';
2497  }
2498  
2499  /**
2500   * Rebuild list of date formats when modules list is saved.
2501   */
2502  function date_api_system_modules_submit($form, &$form_state) {
2503    date_formats_rebuild();
2504  }
2505  
2506  /**
2507   * Implementation of hook_form_alter().
2508   *
2509   * Remove the 'date_formats' section from the 'admin/settings/date-time' page.
2510   * This form section is now part of the form at 'admin/settings/date-time/formats'.
2511   * We add the formats as values to the form to avoid errors on submission
2512   * of the form when expected values are missing in system_date_time_settings_submit().
2513   *
2514   * Add a form element to configure whether or not week numbers are ISO-8601 (default: FALSE == US/UK/AUS norm).
2515   */
2516  function date_api_form_system_date_time_settings_alter(&$form, $form_state, $form_id = 'system_date_time_settings') {
2517    include_once(drupal_get_path('module', 'date_api') .'/date_api.admin.inc');
2518    $formats_form = date_api_date_formats_form($form_state);
2519    $form['date_formats'] = $formats_form['date_formats'];
2520    foreach ($form['date_formats'] as $key => $value) {
2521      if (substr($key, 0, 1) != '#') {
2522        $form['date_formats'][$key]['#type'] = 'value';
2523      }
2524      else {
2525        unset($form['date_formats'][$key]);
2526      }
2527    }
2528    $form['locale']['date_api_use_iso8601'] = array(
2529      '#type'          => 'checkbox',
2530      '#title'         => t('Use ISO-8601 week numbers'),
2531      '#default_value' => variable_get('date_api_use_iso8601', FALSE),
2532      '#description'   => t('IMPORTANT! If checked, First day of week MUST be set to Monday'),
2533    );
2534    $form['#validate'][] = 'date_api_form_system_settings_validate';
2535  }
2536  
2537  /**
2538   * Validate that the option to use ISO weeks matches first day of week choice.
2539   */
2540  function date_api_form_system_settings_validate(&$form, &$form_state) {
2541    $form_values = $form_state['values'];
2542    if ($form_values['date_api_use_iso8601'] && $form_values['date_first_day'] != 1) {
2543      form_set_error('date_first_day', t('When using ISO-8601 week numbers, the first day of the week must be set to Monday.'));
2544    }
2545  }
2546  
2547  /**
2548   * Helper function; add system.js and javascript settings.
2549   */
2550  function date_api_add_system_javascript() {
2551    drupal_add_js(drupal_get_path('module', 'date_api') .'/date_api.js', 'module');
2552    drupal_add_js(array('dateDateTime' => array('lookup' => url('admin/settings/date-time/formats/lookup'))), 'setting');
2553  }
2554  
2555  /**
2556   * Return the date for a given format string via Ajax.
2557   */
2558  function date_api_date_time_lookup() {
2559    $result = date_format_date(date_now(), 'custom', $_GET['format']);
2560    echo drupal_to_js($result);
2561    exit;
2562  }
2563  
2564  /*
2565   * Test validity of a date range string.
2566   */
2567  function date_range_valid($string) {
2568    $matches = preg_match('@^(\-[0-9]+|[0-9]{4}):([\+|\-][0-9]+|[0-9]{4})$@', $string);
2569    return $matches < 1 ? FALSE : TRUE;
2570  }
2571  
2572  /**
2573   * Split a string like -3:+3 or 2001:2010 into 
2574   * an array of min and max years.
2575   * 
2576   * Center the range around the current year, if any, but expand it far
2577   * enough so it will pick up the year value in the field in case
2578   * the value in the field is outside the initial range.
2579   */
2580  function date_range_years($string, $date = NULL) {
2581    $this_year = date_format(date_now(), 'Y');
2582    list($min_year, $max_year) = explode(':', $string);
2583  
2584    // Valid patterns would be -5:+5, 0:+1, 2008:2010.
2585    $plus_pattern = '@[\+|\-][0-9]{1,4}@';
2586    $year_pattern = '@[0-9]{4}@';
2587    if (!preg_match($year_pattern, $min_year, $matches)) {
2588      if (preg_match($plus_pattern, $min_year, $matches)) {
2589        $min_year = $this_year + $matches[0];
2590      }
2591      else {
2592        $min_year = $this_year;
2593      }
2594    }
2595    if (!preg_match($year_pattern, $max_year, $matches)) {
2596      if (preg_match($plus_pattern, $max_year, $matches)) {
2597        $max_year = $this_year + $matches[0];
2598      }
2599      else {
2600        $max_year = $this_year;
2601      }
2602    }
2603    // We expect the $min year to be less than the $max year.
2604    // Some custom values for -99:+99 might not obey that.
2605    if ($min_year > $max_year) {
2606      $temp = $max_year;
2607      $max_year = $min_year;
2608      $min_year = $temp;
2609    }
2610    // If there is a current value, stretch the range to include it.
2611    $value_year = is_object($date) ? date_format($date, 'Y') : '';
2612    if (!empty($value_year)) {
2613      $min_year = min($value_year, $min_year);
2614      $max_year = max($value_year, $max_year);
2615    }
2616    return array($min_year, $max_year);
2617  }
2618  
2619  /**
2620   * Convert a min and max year into a string like '-3:+1'.
2621   *
2622   * @param unknown_type $years
2623   * @return unknown
2624   */
2625  function date_range_string($years) {
2626    $this_year = date_format(date_now(), 'Y');
2627    if ($years[0] < $this_year) {
2628      $min = '-'. ($this_year - $years[0]);
2629    }
2630    else {
2631      $min = '+'. ($years[0] - $this_year);
2632    }
2633    if ($years[1] < $this_year) {
2634      $max = '-'. ($this_year - $years[1]);
2635    }
2636    else {
2637      $max = '+'. ($years[1] - $this_year);
2638    }
2639    return $min .':'. $max;
2640  }
2641  
2642  /**
2643   * Implement hook_date_api_tables().
2644   */
2645  function date_api_date_api_tables() {
2646    return array('node', 'comments', 'users');
2647  }
2648  
2649  /**
2650   * Determine if a from/to date combination qualify as 'All day'.
2651   *
2652   * @param object $date1, a string date in datetime format for the 'from' date.
2653   * @param object $date2, a string date in datetime format for the 'to' date.
2654   * @return TRUE or FALSE.
2655   */
2656  function date_is_all_day($string1, $string2, $granularity = 'second', $increment = 1) {
2657    if (empty($string1) || empty($string2)) {
2658      return FALSE;
2659    }
2660    elseif (!in_array($granularity, array('hour', 'minute', 'second'))) {
2661      return FALSE;
2662    }
2663  
2664    preg_match('/([0-9]{4}-[0-9]{2}-[0-9]{2}) (([0-9]{2}):([0-9]{2}):([0-9]{2}))/', $string1, $matches);
2665    $count = count($matches);
2666    $date1 = $count > 1 ? $matches[1] : '';
2667    $time1 = $count > 2 ? $matches[2] : '';
2668    $hour1 = $count > 3 ? intval($matches[3]) : 0;
2669    $min1 = $count > 4 ? intval($matches[4]) : 0;
2670    $sec1 = $count > 5 ? intval($matches[5]) : 0;
2671    preg_match('/([0-9]{4}-[0-9]{2}-[0-9]{2}) (([0-9]{2}):([0-9]{2}):([0-9]{2}))/', $string2, $matches);
2672    $count = count($matches);
2673    $date2 = $count > 1 ? $matches[1] : '';
2674    $time2 = $count > 2 ? $matches[2] : '';
2675    $hour2 = $count > 3 ? intval($matches[3]) : 0;
2676    $min2 = $count > 4 ? intval($matches[4]) : 0;
2677    $sec2 = $count > 5 ? intval($matches[5]) : 0;
2678  
2679    if (empty($date1) || empty($date2)) {
2680      return FALSE;
2681    }
2682    if (empty($time1) || empty($time2)) {
2683      return FALSE;
2684    }
2685  
2686    $tmp = date_seconds('s', TRUE, $increment);
2687    $max_seconds = intval(array_pop($tmp));
2688    $tmp = date_minutes('i', TRUE, $increment);
2689    $max_minutes = intval(array_pop($tmp));
2690  
2691    switch ($granularity) {
2692      case 'second':
2693        $min_match = $time1 == '00:00:00' || ($hour1 == 0 && $min1 == 0 && $sec1 == 0);
2694        $max_match = $time2 == '00:00:00' || ($hour2 == 23 && $min2 == $max_minutes && $sec2 == $max_seconds) || ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0 && $sec1 == 0 && $sec2 == 0);
2695        break;
2696      case 'minute':
2697        $min_match = $time1 == '00:00:00' || ($hour1 == 0 && $min1 == 0);
2698        $max_match = $time2 == '00:00:00' || ($hour2 == 23 && $min2 == $max_minutes) || ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0);
2699        break;
2700      case 'hour':
2701        $min_match = $time1 == '00:00:00' || ($hour1 == 0);
2702        $max_match = $time2 == '00:00:00' || ($hour2 == 23) || ($hour1 == 0 && $hour2 == 0);
2703        break;
2704      default:
2705        $min_match = TRUE;
2706        $max_match = FALSE;
2707    }
2708  
2709    if ($min_match && $max_match) {
2710      return TRUE;
2711    }
2712  
2713    return FALSE;
2714  }
2715  
2716  
2717  /**
2718   * Helper function to round minutes and seconds to requested value.
2719   */
2720  function date_increment_round(&$date, $increment) {
2721    // Round minutes and seconds, if necessary.
2722    if (is_object($date) && $increment > 1) {
2723      $day = intval(date_format($date, 'j'));
2724      $hour = intval(date_format($date, 'H'));
2725      $second = intval(round(intval(date_format($date, 's')) / $increment) * $increment);
2726      $minute = intval(date_format($date, 'i'));
2727      if ($second == 60) {
2728        $minute += 1;
2729        $second = 0;
2730      }
2731      $minute = intval(round($minute / $increment) * $increment);
2732      if ($minute == 60) {
2733        $hour += 1;
2734        $minute = 0;
2735      }
2736      date_time_set($date, $hour, $minute, $second);
2737      if ($hour == 24) {
2738        $day += 1;
2739        $hour = 0;
2740        $year = date_format($date, 'Y');
2741        $month = date_format($date, 'n');
2742        date_date_set($date, $year, $month, $day);
2743      }
2744    }
2745    return $date;
2746  }
2747  
2748  /**
2749   * Return the nested form elements for a field by name.
2750   * This can be used either to retrieve the entire sub-element
2751   * for a field by name, no matter how deeply nested it is within
2752   * fieldgroups or multigroups, or to find the multiple value
2753   * sub-elements within a field element by name (i.e. 'value' or
2754   * 'rrule'). You can also use this function to see if an item exists 
2755   * in a form (the return will be an empty array if it does not exist).
2756   *
2757   * The function returns an array of results. A field will generally
2758   * only exist once in a form but the function can also be used to 
2759   * locate all the 'value' elements within a multiple value field,
2760   * so the result is always returned as an array of values.
2761   *
2762   * For example, for a field named field_custom,  the following will 
2763   * pluck out the form elements for this field from the node form, 
2764   * no matter how deeply it is nested within fieldgroups or fieldsets:
2765   *
2766   * $elements = content_get_nested_elements($node_form, 'field_custom');
2767   *
2768   * You can prefix the function with '&' to retrieve the element by 
2769   * reference to alter it directly:
2770   *
2771   * $elements = &content_get_nested_elements($form, 'field_custom');
2772   * foreach ($elements as $element) {
2773   *   $element['#after_build'][] = 'my_field_afterbuild';
2774   * }
2775   *
2776   * During the #after_build you could then do something like the
2777   * following to alter each individual part of a multiple value field:
2778   *
2779   * $sub_elements = &content_get_nested_elements($element, 'value');
2780   * foreach ($sub_elements as $sub_element) {
2781   *   $sub_element['#element_validate'][] = 'custom_validation';
2782   * }
2783   *
2784   * @param $form
2785   *   The form array to search.
2786   * @param $field_name
2787   *   The name or key of the form elements to return.
2788   * @return
2789   *   An array of all matching form elements, returned by reference.
2790   */
2791  function &date_get_nested_elements(&$form, $field_name) {
2792    $elements = array();
2793  
2794    foreach (element_children($form) as $key) {
2795      if ($key === $field_name) {
2796        $elements[] = &$form[$key];
2797      }
2798      else if (is_array($form[$key])) {
2799        $nested_form = &$form[$key];
2800        if ($sub_elements = &date_get_nested_elements($nested_form, $field_name)) {
2801          $elements = array_merge($elements, $sub_elements);
2802        }
2803      }
2804    }
2805  
2806    return $elements;
2807  }


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7