You are here

date.module in Date 8

Same filename and directory in other branches
  1. 5 date.module
  2. 7.3 date.module
  3. 7 date.module
  4. 7.2 date.module

Defines date/time field types.

File

date.module
View source
<?php

/**
 * @file
 * Defines date/time field types.
 */
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\date_api\DateGranularity;
module_load_include('theme', 'date', 'date');
module_load_include('inc', 'date', 'date.field');
module_load_include('inc', 'date', 'date_elements');

/**
 * Custom validation for the timestamp form element.
 * The date element contains an ISO date created from user input,
 * convert it back to a timestamp.
 */
function _timestamp_validate(&$element, &$form_state) {
  $input_exists = FALSE;
  $input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists);
  if ($input_exists) {
    $date = new DrupalDateTime($input['value'], $element['value']['#date_timezone']);
    dsm($date);

    // If this is an all day date, set the time back to midnight before saving it.
    if (!empty($input['all_day'])) {
      $date
        ->setTime(0, 0, 0);
    }
    $date
      ->setTimeZone(timezone_open('UTC'));
    form_set_value($element['value'], $date
      ->getTimestamp(), $form_state);
  }
}

/**
 * Helper function to figure out the bundle name for an entity.
 */
function date_get_entity_bundle($entity_type, $entity) {
  switch ($entity_type) {
    case 'field_collection_item':
      $bundle = $entity->field_name;
      break;
    default:
      $bundle = field_extract_bundle($entity_type, $entity);
      break;
  }

  // If there is no bundle name, field_info() uses the entity name as the bundle
  // name in its arrays.
  if (empty($bundle)) {
    $bundle = $entity_type;
  }
  return $bundle;
}

/**
 * Wrapper function around each of the widget types for creating a date object.
 */
function date_input_date($field, $instance, $element, $input) {
  switch ($instance['widget']['type']) {
    case 'date_popup':
      $function = 'datetime_get_input_date';
      break;
    default:
      $function = 'datelist_get_input_date';
  }
  return $function($element, $input);
}

/**
 * Implements hook_theme().
 */
function date_theme() {
  $path = drupal_get_path('module', 'date');
  module_load_include('theme', 'date', 'date');
  $base = array(
    'file' => 'date.theme',
    'path' => "{$path}",
  );
  $themes = array(
    'date_combo' => $base + array(
      'render element' => 'element',
    ),
    'date_text_parts' => $base + array(
      'render element' => 'element',
    ),
    'date' => $base + array(
      'render element' => 'element',
    ),
    'date_display_single' => $base + array(
      'variables' => array(
        'date' => NULL,
        'timezone' => NULL,
        'dates' => NULL,
        'attributes' => array(),
        'rdf_mapping' => NULL,
        'add_rdf' => NULL,
      ),
    ),
    'date_display_range' => $base + array(
      'variables' => array(
        'date1' => NULL,
        'date2' => NULL,
        'timezone' => NULL,
        'dates' => NULL,
        // HTML attributes that will be applied to both the start and end dates
        // (unless overridden).
        'attributes' => array(),
        // HTML attributes that will be applied to the start date only.
        'attributes_start' => array(),
        // HTML attributes that will be applied to the end date only.
        'attributes_end' => array(),
        'rdf_mapping' => NULL,
        'add_rdf' => NULL,
      ),
    ),
    'date_display_combination' => $base + array(
      'variables' => array(
        'entity_type' => NULL,
        'entity' => NULL,
        'field' => NULL,
        'instance' => NULL,
        'langcode' => NULL,
        'item' => NULL,
        'delta' => NULL,
        'display' => NULL,
        'dates' => NULL,
        'attributes' => array(),
        'rdf_mapping' => NULL,
        'add_rdf' => NULL,
      ),
    ),
    'date_display_interval' => $base + array(
      'variables' => array(
        'entity_type' => NULL,
        'entity' => NULL,
        'field' => NULL,
        'instance' => NULL,
        'langcode' => NULL,
        'item' => NULL,
        'delta' => NULL,
        'display' => NULL,
        'dates' => NULL,
        'attributes' => array(),
        'rdf_mapping' => NULL,
        'add_rdf' => NULL,
      ),
    ),
  );
  return $themes;
}

/**
 * Implements hook_element_info().
 *
 * date_combo will create a 'start' and optional 'end' date, along with
 * an optional 'timezone' column for date-specific timezones. Each
 * 'start' and 'end' date will be constructed from date_select.
 */
function date_element_info() {
  $type = array();
  $type['date_combo'] = array(
    '#input' => TRUE,
    '#delta' => 0,
    '#columns' => array(
      'value',
      'value2',
      'timezone',
      'offset',
      'offset2',
    ),
    '#process' => array(
      'date_combo_element_process',
    ),
    '#element_validate' => array(
      'date_combo_validate',
    ),
    '#theme_wrappers' => array(
      'date_combo',
    ),
  );
  if (module_exists('ctools')) {
    $type['date_combo']['#pre_render'] = array(
      'ctools_dependent_pre_render',
    );
  }
  return $type;
}

/**
 * Helper function for creating formatted date arrays from a formatter.
 *
 * Use the Date API to get an object representation of a date field.
 *
 * @param string $formatter
 *   The date formatter.
 * @param string $entity_type
 *   The entity_type for the instance
 * @param object $entity
 *   The entity object.
 * @param array $field
 *   The field info array.
 * @param array $instance
 *   The field instance array.
 * @param string $langcode
 *   The language code used by this field.
 * @param array $item
 *   An entity field item, like $entity->myfield[0].
 * @param array $display
 *   The instance display settings.
 *
 * @return array
 *   An array that holds the Start and End date objects.
 *   Each date object looks like:
 *     date [value] => array (
 *       [db] => array (  // the value stored in the database
 *         [object] => the datetime object
 *         [datetime] => 2007-02-15 20:00:00
 *       )
 *       [local] => array (  // the local representation of that value
 *         [object] => the datetime object
 *         [datetime] => 2007-02-15 14:00:00
 *         [timezone] => US/Central
 *         [offset] => -21600
 *       )
 *     )
 */
function date_formatter_process($formatter, $entity, $field, $instance, $langcode, $item, $settings) {
  $dates = array();
  $timezone = drupal_get_user_timezone();
  if (empty($timezone)) {
    return $dates;
  }
  $granularity = date_granularity($field);
  $field_name = $field['field_name'];
  $format = date_formatter_format($formatter, $settings, $granularity, $langcode);
  $timezone = isset($item['timezone']) ? $item['timezone'] : '';
  $timezone = date_get_timezone($field['settings']['tz_handling'], $timezone);
  $timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
  $db_format = date_type_format($field['type']);
  $keys = date_available_values($field);
  foreach ($keys as $key) {
    if (empty($item[$key])) {
      $dates[$key] = NULL;
    }
    else {

      // Create a date object with a GMT timezone from the database value.
      $dates[$key] = array();

      // Check to see if this date was already created by date_field_load().
      if (isset($item['db'][$key])) {
        $date = $item['db'][$key];
      }
      else {
        $date = new DrupalDateTime($item[$key], $timezone_db, $db_format);
        $date->granularity = $granularity;
      }
      $dates[$key]['db']['object'] = $date;
      $dates[$key]['db']['datetime'] = date_format($date, DATE_FORMAT_DATETIME);
      date_timezone_set($date, timezone_open($timezone));
      $dates[$key]['local']['object'] = $date;
      $dates[$key]['local']['datetime'] = date_format($date, DATE_FORMAT_DATETIME);
      $dates[$key]['local']['timezone'] = $timezone;
      $dates[$key]['local']['offset'] = date_offset_get($date);

      // Format the date, special casing the 'interval' format which doesn't
      // need to be processed.
      $dates[$key]['formatted'] = '';
      $dates[$key]['formatted_iso'] = $date
        ->format('c');
      if ($date instanceof DrupalDateTime) {
        if ($format == 'format_interval') {
          $dates[$key]['interval'] = date_format_interval($date);
        }
        elseif ($format == 'format_calendar_day') {
          $dates[$key]['calendar_day'] = date_format_calendar_day($date);
        }
        elseif ($format == 'U') {
          $dates[$key]['formatted'] = $date
            ->format($format);
          $dates[$key]['formatted_date'] = $date
            ->format($format);
          $dates[$key]['formatted_time'] = '';
          $dates[$key]['formatted_timezone'] = '';
        }
        elseif (!empty($format)) {
          $dates[$key]['formatted'] = $date
            ->format($format);
          $dates[$key]['formatted_date'] = $date
            ->format(DateGranularity::limitFormat($format, DateGranularity::$date_parts));
          $dates[$key]['formatted_time'] = $date
            ->format(DateGranularity::limitFormat($format, DateGranularity::$time_parts));
          $dates[$key]['formatted_timezone'] = $date
            ->format(DateGranularity::limitFormat($format, array(
            'timezone',
          )));
        }
      }
    }
  }
  if (empty($dates['value2'])) {
    $dates['value2'] = $dates['value'];
  }

  // Allow other modules to alter the date values.
  $context = array(
    'field' => $field,
    'instance' => $instance,
    'format' => $format,
    'entity' => $entity,
    'langcode' => $langcode,
    'item' => $item,
    'settings' => $settings,
  );
  drupal_alter('date_formatter_dates', $dates, $context);
  $dates['format'] = $format;
  return $dates;
}

/**
 * Retrieves the granularity for a field.
 *
 * $field['settings']['granularity'] will contain an array like
 * ('hour' => 'hour', 'month' => 0) where the values turned on return their own
 * names and the values turned off return a zero need to reconfigure this into
 * simple array of the turned on values
 *
 * @param array $field
 *   The field array.
 */
function date_granularity($field) {
  if (!is_array($field) || !is_array($field['settings']['granularity'])) {
    $field['settings']['granularity'] = drupal_map_assoc(array(
      'year',
      'month',
      'day',
    ));
  }
  return array_values(array_filter($field['settings']['granularity']));
}

/**
 * Helper function to create an array of the date values in a
 * field that need to be processed.
 */
function date_available_values($field) {
  return $field['settings']['todate'] ? array(
    'value',
    'value2',
  ) : array(
    'value',
  );
}

/**
 * Helper function to retrieve the possible keys stored in the data array.
 */
function date_data_keys() {
  return array(
    'all_day',
    'all_day2',
    'rrule',
  );
}

/**
 * Implements hook_form_FORM_ID_alter() for field_ui_field_edit_form().
 */
function date_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
  $field = $form['#field'];
  $instance = $form['#instance'];
  if ($field['type'] != 'date') {
    return;
  }

  // Reorganize the instance settings and widget settings sections into a more
  // intuitive combined fieldset.
  $form['instance']['defaults'] = array(
    '#type' => 'fieldset',
    '#title' => t('More settings and values'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['instance']['date_format'] = array(
    '#type' => 'fieldset',
    '#title' => t('Date entry'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['instance']['default_values'] = array(
    '#type' => 'fieldset',
    '#title' => t('Default values'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['instance']['years_back_and_forward'] = array(
    '#type' => 'fieldset',
    '#title' => t('Starting and ending year'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['instance']['#pre_render'][] = 'date_field_ui_field_edit_form_pre_render';
}

/**
 * Rearrange form elements into fieldsets for presentation only.
 */
function date_field_ui_field_edit_form_pre_render($form) {
  foreach ($form as $name => $element) {
    if (is_array($element) && isset($element['#fieldset'])) {
      $fieldset = $element['#fieldset'];
      $form[$fieldset][$name] = $element;
      unset($form[$name]);
    }
  }
  foreach (array(
    'date_format',
    'default_values',
    'years_back_and_forward',
  ) as $name) {
    if (element_children($form[$name])) {

      // Force the items in the fieldset to be resorted now that the instance
      // and widget settings are combined.
      $form[$name]['#sorted'] = FALSE;
      $form['defaults'][$name] = $form[$name];
    }
    unset($form[$name]);
  }
  return $form;
}

/**
 * Implements hook_field_widget_error().
 */
function date_field_widget_error($element, $error, $form, &$form_state) {
  form_error($element[$error['error']], $error['message']);
}

/**
 * Retrieve a date format string from formatter settings.
 */
function date_formatter_format($formatter, $settings, $granularity = array(), $langcode = NULL) {
  $format_type = !empty($settings['format_type']) ? $settings['format_type'] : 'format_interval';
  switch ($formatter) {
    case 'format_interval':
      return 'format_interval';
      break;
    case 'date_plain':
      return 'date_plain';
      break;
    default:
      $format = date_format_type_format($format_type, $langcode);
      break;
  }

  // A selected format might include timezone information.
  array_push($granularity, 'timezone');
  return DateGranularity::limitFormat($format, $granularity);
}

/**
 * Implements hook_field_widget_WIDGETTYPE_form_alter().
 */
function date_field_widget_date_popup_form_alter($element, $form_state, $context) {
}

/**
 * Helper function to get the right format for a format type.
 * Checks for locale-based format first.
 */
function date_format_type_format($format_type, $langcode = NULL) {
  $static =& drupal_static(__FUNCTION__);
  if (!isset($static[$langcode][$format_type])) {
    $format = system_date_format_locale($langcode, $format_type);

    // If locale enabled and $format_type inexistent in {date_format_locale}
    // we receive (due to inconsistency of core api) an array of all (other)
    // formats available for $langcode in locale table.
    // However there's no guarantee that the key $format_type exists.
    // See http://drupal.org/node/1302358.
    if (!is_string($format)) {

      // If the configuration page at admin/config/regional/date-time was
      // never saved, the default core date format variables
      // ('date_format_short', 'date_format_medium', and 'date_format_long')
      // will not be stored in the database, so we have to define their
      // expected defaults here.
      switch ($format_type) {
        case 'short':
          $default = 'm/d/Y - H:i';
          break;
        case 'long':
          $default = 'l, F j, Y - H:i';
          break;

        // If it's not one of the core date types and isn't stored in the
        // database, we'll fall back on using the same default format as the
        // 'medium' type.
        case 'medium':
        default:

          // @todo: If a non-core module provides a date type and does not
          //   variable_set() a default for it, the default assumed here may
          //   not be correct (since the default format used by 'medium' may
          //   not even be one of the allowed formats for the date type in
          //   question). To fix this properly, we should really call
          //   system_get_date_formats($format_type) and take the first
          //   format from that list as the default. However, this function
          //   is called often (on many different page requests), so calling
          //   system_get_date_formats() from here would be a performance hit
          //   since that function writes several records to the database
          //   during each page request that calls it.
          $default = 'D, m/d/Y - H:i';
          break;
      }
      $format = variable_get('date_format_' . $format_type, $default);
    }
    $static[$langcode][$format_type] = $format;
  }
  return $static[$langcode][$format_type];
}

/**
 * Helper function to adapt entity date fields to formatter settings.
 */
function date_prepare_entity($formatter, $entity_type, $entity, $field, $instance, $langcode, $item, $display) {

  // If there are options to limit multiple values,
  // alter the entity values to match.
  $field_name = $field['field_name'];
  $options = $display['settings'];
  $max_count = $options['multiple_number'];

  // If no results should be shown, empty the values and return.
  if (is_numeric($max_count) && $max_count == 0) {
    $entity->{$field_name} = array();
    return $entity;
  }

  // Otherwise removed values that should not be displayed.
  if (!empty($options['multiple_from']) || !empty($options['multiple_to']) || !empty($max_count)) {
    $date_api_config = config('date_api.info');
    $format = date_type_format($field['type']);
    include_once drupal_get_path('module', 'date_api') . '/date_api_sql.inc';
    $date_handler = new DateSqlHandler($field);
    $arg0 = !empty($options['multiple_from']) ? $date_handler
      ->arg_replace($options['multiple_from']) : $date_api_config
      ->get('year.min') . '-01-01T00:00:00';
    $arg1 = !empty($options['multiple_to']) ? $date_handler
      ->arg_replace($options['multiple_to']) : $date_api_config
      ->get('year.max') . '-12-31T23:59:59';
    if (!empty($arg0) && !empty($arg1)) {
      $arg = $arg0 . '--' . $arg1;
    }
    elseif (!empty($arg0)) {
      $arg = $arg0;
    }
    elseif (!empty($arg1)) {
      $arg = $arg1;
    }
    if (!empty($arg)) {
      $range = $date_handler
        ->arg_range($arg);
      $start = date_format($range[0], $format);
      $end = date_format($range[1], $format);

      // Empty out values we don't want to see.
      $count = 0;
      foreach ($entity->{$field_name}[$langcode] as $delta => $value) {
        if (!empty($entity->date_repeat_show_all)) {
          break;
        }
        elseif (!empty($max_count) && is_numeric($max_count) && $count >= $max_count || !empty($value['value']) && $value['value'] < $start || !empty($value['value2']) && $value['value2'] > $end) {
          unset($entity->{$field_name}[$langcode][$delta]);
        }
        else {
          $count++;
        }
      }
    }
  }
  return $entity;
}

/**
 * The callback for setting a default value for an empty date field.
 */
function date_default_value($entity_type, $entity, $field, $instance, $langcode) {
  $item = array();
  $db_format = date_type_format($field['type']);
  $date = date_default_value_part($item, $field, $instance, $langcode, 'value');
  $item[0]['value'] = $date instanceof DrupalDateTime ? date_format($date, $db_format) : '';
  if (!empty($field['settings']['todate'])) {
    $date = date_default_value_part($item, $field, $instance, $langcode, 'value2');
    $item[0]['value2'] = $date instanceof DrupalDateTime ? date_format($date, $db_format) : '';
  }

  // Make sure the default value has the same construct as a loaded field value
  // to avoid errors if the default value is used on a hidden element.
  $item[0]['timezone'] = date_get_timezone($field['settings']['tz_handling']);
  $item[0]['timezone_db'] = date_get_timezone_db($field['settings']['tz_handling']);
  $item[0]['date_type'] = $field['type'];
  if (!isset($item[0]['value2'])) {
    $item[0]['value2'] = $item[0]['value'];
  }
  return $item;
}

/**
 * Helper function for the date default value callback to set
 * either 'value' or 'value2' to its default value.
 */
function date_default_value_part($item, $field, $instance, $langcode, $part = 'value') {
  $timezone = date_get_timezone($field['settings']['tz_handling']);
  $timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
  $date = NULL;
  if ($part == 'value') {
    $default_value = $instance['settings']['default_value'];
    $default_value_code = $instance['settings']['default_value_code'];
  }
  else {
    $default_value = $instance['settings']['default_value2'];
    $default_value_code = $instance['settings']['default_value_code2'];
  }
  if (empty($default_value) || $default_value == 'blank') {
    return NULL;
  }
  elseif ($default_value == 'strtotime' && !empty($default_value_code)) {
    $date = new DrupalDateTime($default_value_code, drupal_get_user_timezone());
  }
  elseif ($part == 'value2' && $default_value == 'same') {
    if ($instance['settings']['default_value'] == 'blank' || empty($item[0]['value'])) {
      return NULL;
    }
    else {

      // The date stored in 'value' has already been switched to the db timezone.
      $date = new DrupalDateTime($item[0]['value'], $timezone_db, DATE_FORMAT_DATETIME);
    }
  }
  elseif ($field['settings']['tz_handling'] == 'none') {
    $date = new DrupalDateTime();
  }
  else {
    $date = new DrupalDateTime('now', $timezone);
  }

  // The default value needs to be in the database timezone.
  date_timezone_set($date, timezone_open($timezone_db));
  return $date;
}

/**
 * Form validation handler for _date_field_widget_settings_form().
 */
function date_field_widget_settings_form_validate(&$form, &$form_state) {

  // The widget settings are in the wrong place in the form because of #tree on
  // the top level.
  $settings = $form_state['values']['instance']['widget']['settings'];
  $settings = array_merge($settings, $settings['advanced']);
  unset($settings['advanced']);
  form_set_value(array(
    '#parents' => array(
      'instance',
      'widget',
      'settings',
    ),
  ), $settings, $form_state);
  $widget =& $form_state['values']['instance']['widget'];

  // Munge the table display for text parts back into an array of text parts.
  if (is_array($widget['settings']['text_parts'])) {
    form_set_value($form['text_parts'], array_keys(array_filter($widget['settings']['text_parts'])), $form_state);
  }
  if ($widget['settings']['input_format'] === 'custom' && empty($widget['settings']['input_format_custom'])) {
    form_set_error('instance][widget][settings][input_format_custom', t('Please enter a custom date format, or choose one of the preset formats.'));
  }
  switch ($widget['settings']['date_date_type']) {
    case 'date':
      $widget['settings']['date_date_format'] = variable_get('date_format_html_date', 'Y-m-d');
      break;
  }
  switch ($widget['settings']['date_time_type']) {
    case 'time':
      $widget['settings']['date_times_format'] = variable_get('date_format_html_time', 'H:i:s');
      break;
  }
}

Functions

Namesort descending Description
date_available_values Helper function to create an array of the date values in a field that need to be processed.
date_data_keys Helper function to retrieve the possible keys stored in the data array.
date_default_value The callback for setting a default value for an empty date field.
date_default_value_part Helper function for the date default value callback to set either 'value' or 'value2' to its default value.
date_element_info Implements hook_element_info().
date_field_ui_field_edit_form_pre_render Rearrange form elements into fieldsets for presentation only.
date_field_widget_date_popup_form_alter Implements hook_field_widget_WIDGETTYPE_form_alter().
date_field_widget_error Implements hook_field_widget_error().
date_field_widget_settings_form_validate Form validation handler for _date_field_widget_settings_form().
date_formatter_format Retrieve a date format string from formatter settings.
date_formatter_process Helper function for creating formatted date arrays from a formatter.
date_format_type_format Helper function to get the right format for a format type. Checks for locale-based format first.
date_form_field_ui_field_edit_form_alter Implements hook_form_FORM_ID_alter() for field_ui_field_edit_form().
date_get_entity_bundle Helper function to figure out the bundle name for an entity.
date_granularity Retrieves the granularity for a field.
date_input_date Wrapper function around each of the widget types for creating a date object.
date_prepare_entity Helper function to adapt entity date fields to formatter settings.
date_theme Implements hook_theme().
_timestamp_validate Custom validation for the timestamp form element. The date element contains an ISO date created from user input, convert it back to a timestamp.