getPattern($format_type); } if ($time_format_entity = entity_load('date_format', 'html_time')) { $time_format = $time_format_entity->getPattern($format_type); } } $types['datetime'] = array( '#input' => TRUE, '#element_validate' => array('datetime_datetime_validate'), '#process' => array('datetime_datetime_form_process', 'form_process_group'), '#pre_render' => array('form_pre_render_group'), '#theme' => 'datetime_form', '#theme_wrappers' => array('datetime_wrapper'), '#date_date_format' => $date_format, '#date_format_string_type' => $format_type, '#date_date_element' => 'date', '#date_date_callbacks' => array(), '#date_time_format' => $time_format, '#date_time_element' => 'time', '#date_time_callbacks' => array(), '#date_year_range' => '1900:2050', '#date_increment' => 1, '#date_timezone' => '', ); $types['datelist'] = array( '#input' => TRUE, '#element_validate' => array('datetime_datelist_validate'), '#process' => array('datetime_datelist_form_process'), '#theme' => 'datetime_form', '#theme_wrappers' => array('datetime_wrapper'), '#date_part_order' => array('year', 'month', 'day', 'hour', 'minute'), '#date_year_range' => '1900:2050', '#date_increment' => 1, '#date_date_callbacks' => array(), '#date_timezone' => '', ); return $types; } /** * Implements hook_theme(). */ function datetime_theme() { return array( 'datetime_form' => array( 'template' => 'datetime-form', 'render element' => 'element', ), 'datetime_wrapper' => array( 'template' => 'datetime-wrapper', 'render element' => 'element', ), ); } /** * Validation callback for the datetime widget element. * * The date has already been validated by the datetime form type validator and * transformed to an date object. We just need to convert the date back to a the * storage timezone and format. * * @param array $element * The form element whose value is being validated. * @param array $form_state * The current state of the form. */ function datetime_datetime_widget_validate(&$element, &$form_state) { if (!form_get_errors($form_state)) { $input_exists = FALSE; $input = NestedArray::getValue($form_state['values'], $element['#parents'], $input_exists); if ($input_exists) { // The date should have been returned to a date object at this point by // datetime_validate(), which runs before this. if (!empty($input['value'])) { $date = $input['value']; if ($date instanceOf DrupalDateTime && !$date->hasErrors()) { // If this is a date-only field, set it to the default time so the // timezone conversion can be reversed. if ($element['value']['#date_time_element'] == 'none') { datetime_date_default_time($date); } // Adjust the date for storage. $date->setTimezone(new \DateTimezone(DATETIME_STORAGE_TIMEZONE)); $value = $date->format($element['value']['#date_storage_format']); form_set_value($element['value'], $value, $form_state); } } } } } /** * Validation callback for the datelist widget element. * * The date has already been validated by the datetime form type validator and * transformed to an date object. We just need to convert the date back to a the * storage timezone and format. * * @param array $element * The form element whose value is being validated. * @param array $form_state * The current state of the form. */ function datetime_datelist_widget_validate(&$element, &$form_state) { if (!form_get_errors($form_state)) { $input_exists = FALSE; $input = NestedArray::getValue($form_state['values'], $element['#parents'], $input_exists); if ($input_exists) { // The date should have been returned to a date object at this point by // datetime_validate(), which runs before this. if (!empty($input['value'])) { $date = $input['value']; if ($date instanceOf DrupalDateTime && !$date->hasErrors()) { // If this is a date-only field, set it to the default time so the // timezone conversion can be reversed. if (!in_array('hour', $element['value']['#date_part_order'])) { datetime_date_default_time($date); } // Adjust the date for storage. $date->setTimezone(new \DateTimezone(DATETIME_STORAGE_TIMEZONE)); $value = $date->format($element['value']['#date_storage_format']); form_set_value($element['value'], $value, $form_state); } } } } } /** * Sets a consistent time on a date without time. * * The default time for a date without time can be anything, so long as it is * consistently applied. If we use noon, dates in most timezones will have the * same value for in both the local timezone and UTC. * * @param $date * */ function datetime_date_default_time($date) { $date->setTime(12, 0, 0); } /** * Prepares variables for datetime form element templates. * * The datetime form element serves as a wrapper around the date element type, * which creates a date and a time component for a date. * * Default template: datetime-form.html.twig. * * @param array $variables * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #title, #value, #options, #description, #required, * #attributes. * * @see form_process_datetime() */ function template_preprocess_datetime_form(&$variables) { $element = $variables['element']; $variables['attributes'] = array(); if (isset($element['#id'])) { $variables['attributes']['id'] = $element['#id']; } if (!empty($element['#attributes']['class'])) { $variables['attributes']['class'] = (array) $element['#attributes']['class']; } $variables['attributes']['class'][] = 'container-inline'; $variables['content'] = $element; } /** * Prepares variables for datetime form wrapper templates. * * Default template: datetime-wrapper.html.twig. * * @param array $variables * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #title, #children, #required, #attributes. */ function template_preprocess_datetime_wrapper(&$variables) { $element = $variables['element']; // If the element is required, a required marker is appended to the label. $variables['required'] = NULL; if(!empty($element['#required'])) { $variables['required'] = array( '#theme' => 'form_required_marker', '#element' => $element, ); } if (!empty($element['#title'])) { $variables['title'] = $element['#title']; } if (!empty($element['#description'])) { $variables['description'] = $element['#description']; } $variables['content'] = $element['#children']; } /** * Expands a datetime element type into date and/or time elements. * * All form elements are designed to have sane defaults so any or all can be * omitted. Both the date and time components are configurable so they can be * output as HTML5 datetime elements or not, as desired. * * Examples of possible configurations include: * HTML5 date and time: * #date_date_element = 'date'; * #date_time_element = 'time'; * HTML5 datetime: * #date_date_element = 'datetime'; * #date_time_element = 'none'; * HTML5 time only: * #date_date_element = 'none'; * #date_time_element = 'time' * Non-HTML5: * #date_date_element = 'text'; * #date_time_element = 'text'; * * Required settings: * - #default_value: A DrupalDateTime object, adjusted to the proper local * timezone. Converting a date stored in the database from UTC to the local * zone and converting it back to UTC before storing it is not handled here. * This element accepts a date as the default value, and then converts the * user input strings back into a new date object on submission. No timezone * adjustment is performed. * Optional properties include: * - #date_date_format: A date format string that describes the format that * should be displayed to the end user for the date. When using HTML5 * elements the format MUST use the appropriate HTML5 format for that * element, no other format will work. See the format_date() function for a * list of the possible formats and HTML5 standards for the HTML5 * requirements. Defaults to the right HTML5 format for the chosen element * if a HTML5 element is used, otherwise defaults to * entity_load('date_format', 'html_date')->getPattern(). * - #date_date_element: The date element. Options are: * - datetime: Use the HTML5 datetime element type. * - datetime-local: Use the HTML5 datetime-local element type. * - date: Use the HTML5 date element type. * - text: No HTML5 element, use a normal text field. * - none: Do not display a date element. * - #date_date_callbacks: Array of optional callbacks for the date element. * Can be used to add a jQuery datepicker. * - #date_time_element: The time element. Options are: * - time: Use a HTML5 time element type. * - text: No HTML5 element, use a normal text field. * - none: Do not display a time element. * - #date_time_format: A date format string that describes the format that * should be displayed to the end user for the time. When using HTML5 * elements the format MUST use the appropriate HTML5 format for that * element, no other format will work. See the format_date() function for * a list of the possible formats and HTML5 standards for the HTML5 * requirements. Defaults to the right HTML5 format for the chosen element * if a HTML5 element is used, otherwise defaults to * entity_load('date_format', 'html_time')->getPattern(). * - #date_time_callbacks: An array of optional callbacks for the time * element. Can be used to add a jQuery timepicker or an 'All day' checkbox. * - #date_year_range: A description of the range of years to allow, like * '1900:2050', '-3:+3' or '2000:+3', where the first value describes the * earliest year and the second the latest year in the range. A year * in either position means that specific year. A +/- value describes a * dynamic value that is that many years earlier or later than the current * year at the time the form is displayed. Used in jQueryUI datepicker year * range and HTML5 min/max date settings. Defaults to '1900:2050'. * - #date_increment: The increment to use for minutes and seconds, i.e. * '15' would show only :00, :15, :30 and :45. Used for HTML5 step values and * jQueryUI datepicker settings. Defaults to 1 to show every minute. * - #date_timezone: The local timezone to use when creating dates. Generally * this should be left empty and it will be set correctly for the user using * the form. Useful if the default value is empty to designate a desired * timezone for dates created in form processing. If a default date is * provided, this value will be ignored, the timezone in the default date * takes precedence. Defaults to the value returned by * drupal_get_user_timezone(). * * Example usage: * @code * $form = array( * '#type' => 'datetime', * '#default_value' => new DrupalDateTime('2000-01-01 00:00:00'), * '#date_date_element' => 'date', * '#date_time_element' => 'none', * '#date_year_range' => '2010:+3', * ); * @endcode * * @param array $element * The form element whose value is being processed. * @param array $form_state * The current state of the form. * * @return array * The form element whose value has been processed. */ function datetime_datetime_form_process($element, &$form_state) { $format_settings = array('format_string_type' => $element['#date_format_string_type']); // The value callback has populated the #value array. $date = !empty($element['#value']['object']) ? $element['#value']['object'] : NULL; // Set a fallback timezone. if ($date instanceOf DrupalDateTime) { $element['#date_timezone'] = $date->getTimezone()->getName(); } elseif (!empty($element['#timezone'])) { $element['#date_timezone'] = $element['#date_timezone']; } else { $element['#date_timezone'] = drupal_get_user_timezone(); } $element['#tree'] = TRUE; if ($element['#date_date_element'] != 'none') { $date_format = $element['#date_date_element'] != 'none' ? datetime_html5_format('date', $element) : ''; $date_value = !empty($date) ? $date->format($date_format, $format_settings) : $element['#value']['date']; // Creating format examples on every individual date item is messy, and // placeholders are invalid for HTML5 date and datetime, so an example // format is appended to the title to appear in tooltips. $extra_attributes = array( 'title' => t('Date (i.e. !format)', array('!format' => datetime_format_example($date_format))), 'type' => $element['#date_date_element'], ); // Adds the HTML5 date attributes. if ($date instanceOf DrupalDateTime && !$date->hasErrors()) { $html5_min = clone($date); $range = datetime_range_years($element['#date_year_range'], $date); $html5_min->setDate($range[0], 1, 1)->setTime(0, 0, 0); $html5_max = clone($date); $html5_max->setDate($range[1], 12, 31)->setTime(23, 59, 59); $extra_attributes += array( 'min' => $html5_min->format($date_format, $format_settings), 'max' => $html5_max->format($date_format, $format_settings), ); } $element['date'] = array( '#type' => 'date', '#title' => t('Date'), '#title_display' => 'invisible', '#value' => $date_value, '#attributes' => $element['#attributes'] + $extra_attributes, '#required' => $element['#required'], '#size' => max(12, strlen($element['#value']['date'])), ); // Allows custom callbacks to alter the element. if (!empty($element['#date_date_callbacks'])) { foreach ($element['#date_date_callbacks'] as $callback) { if (function_exists($callback)) { $callback($element, $form_state, $date); } } } } if ($element['#date_time_element'] != 'none') { $time_format = $element['#date_time_element'] != 'none' ? datetime_html5_format('time', $element) : ''; $time_value = !empty($date) ? $date->format($time_format, $format_settings) : $element['#value']['time']; // Adds the HTML5 attributes. $extra_attributes = array( 'title' =>t('Time (i.e. !format)', array('!format' => datetime_format_example($time_format))), 'type' => $element['#date_time_element'], 'step' => $element['#date_increment'], ); $element['time'] = array( '#type' => 'date', '#title' => t('Time'), '#title_display' => 'invisible', '#value' => $time_value, '#attributes' => $element['#attributes'] + $extra_attributes, '#required' => $element['#required'], '#size' => 12, ); // Allows custom callbacks to alter the element. if (!empty($element['#date_time_callbacks'])) { foreach ($element['#date_time_callbacks'] as $callback) { if (function_exists($callback)) { $callback($element, $form_state, $date); } } } } return $element; } /** * Value callback for a datetime element. * * @param array $element * The form element whose value is being populated. * @param array $input * (optional) The incoming input to populate the form element. If this is * FALSE, the element's default value should be returned. Defaults to FALSE. * * @return array * The data that will appear in the $element_state['values'] collection for * this element. Return nothing to use the default. */ function form_type_datetime_value($element, $input = FALSE) { if ($input !== FALSE) { $date_input = $element['#date_date_element'] != 'none' && !empty($input['date']) ? $input['date'] : ''; $time_input = $element['#date_time_element'] != 'none' && !empty($input['time']) ? $input['time'] : ''; $date_format = $element['#date_date_element'] != 'none' ? datetime_html5_format('date', $element) : ''; $time_format = $element['#date_time_element'] != 'none' ? datetime_html5_format('time', $element) : ''; $timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : NULL; // Seconds will be omitted in a post in case there's no entry. if (!empty($time_input) && strlen($time_input) == 5) { $time_input .= ':00'; } try { $date_time_format = trim($date_format . ' ' . $time_format); $date_time_input = trim($date_input . ' ' . $time_input); $date_time_settings = array('format_string_type' => $element['#date_format_string_type']); $date = DrupalDateTime::createFromFormat($date_time_format, $date_time_input, $timezone, $date_time_settings); } catch (\Exception $e) { $date = NULL; } $input = array( 'date' => $date_input, 'time' => $time_input, 'object' => $date, ); } else { $date = $element['#default_value']; if ($date instanceOf DrupalDateTime && !$date->hasErrors()) { $input = array( 'date' => $date->format($element['#date_date_format'], array('format_string_type' => $element['#date_format_string_type'])), 'time' => $date->format($element['#date_time_format'], array('format_string_type' => $element['#date_format_string_type'])), 'object' => $date, ); } else { $input = array( 'date' => '', 'time' => '', 'object' => NULL, ); } } return $input; } /** * Validation callback for a datetime element. * * If the date is valid, the date object created from the user input is set in * the form for use by the caller. The work of compiling the user input back * into a date object is handled by the value callback, so we can use it here. * We also have the raw input available for validation testing. * * @param array $element * The form element whose value is being validated. * @param array $form_state * The current state of the form. */ function datetime_datetime_validate($element, &$form_state) { $input_exists = FALSE; $input = NestedArray::getValue($form_state['values'], $element['#parents'], $input_exists); if ($input_exists) { $title = !empty($element['#title']) ? $element['#title'] : ''; $date_format = $element['#date_date_element'] != 'none' ? datetime_html5_format('date', $element) : ''; $time_format = $element['#date_time_element'] != 'none' ? datetime_html5_format('time', $element) : ''; $format = trim($date_format . ' ' . $time_format); // If there's empty input and the field is not required, set it to empty. if (empty($input['date']) && empty($input['time']) && !$element['#required']) { form_set_value($element, NULL, $form_state); } // If there's empty input and the field is required, set an error. A // reminder of the required format in the message provides a good UX. elseif (empty($input['date']) && empty($input['time']) && $element['#required']) { form_error($element, $form_state, t('The %field date is required. Please enter a date in the format %format.', array('%field' => $title, '%format' => datetime_format_example($format)))); } else { // If the date is valid, set it. $date = $input['object']; if ($date instanceOf DrupalDateTime && !$date->hasErrors()) { form_set_value($element, $date, $form_state); } // If the date is invalid, set an error. A reminder of the required // format in the message provides a good UX. else { form_error($element, $form_state, t('The %field date is invalid. Please enter a date in the format %format.', array('%field' => $title, '%format' => datetime_format_example($format)))); } } } } /** * Retrieves the right format for a HTML5 date element. * * The format is important because these elements will not work with any other * format. * * @param string $part * The type of element format to retrieve. * @param string $element * The $element to assess. * * @return string * Returns the right format for the type of element, or the original format * if this is not a HTML5 element. */ function datetime_html5_format($part, $element) { $format_type = datetime_default_format_type(); switch ($part) { case 'date': switch ($element['#date_date_element']) { case 'date': return entity_load('date_format', 'html_date')->getPattern($format_type); case 'datetime': case 'datetime-local': return entity_load('date_format', 'html_datetime')->getPattern($format_type); default: return $element['#date_date_format']; } break; case 'time': switch ($element['#date_time_element']) { case 'time': return entity_load('date_format', 'html_time')->getPattern($format_type); default: return $element['#date_time_format']; } break; } } /** * Creates an example for a date format. * * This is centralized for a consistent method of creating these examples. * * @param string $format * * * @return string * */ function datetime_format_example($format) { $format_type = datetime_default_format_type(); $date = &drupal_static(__FUNCTION__); if (empty($date)) { $date = new DrupalDateTime(); } return $date->format($format, array('format_string_type' => $format_type)); } /** * Expands a date element into an array of individual elements. * * Required settings: * - #default_value: A DrupalDateTime object, adjusted to the proper local * timezone. Converting a date stored in the database from UTC to the local * zone and converting it back to UTC before storing it is not handled here. * This element accepts a date as the default value, and then converts the * user input strings back into a new date object on submission. No timezone * adjustment is performed. * Optional properties include: * - #date_part_order: Array of date parts indicating the parts and order * that should be used in the selector, optionally including 'ampm' for * 12 hour time. Default is array('year', 'month', 'day', 'hour', 'minute'). * - #date_text_parts: Array of date parts that should be presented as * text fields instead of drop-down selectors. Default is an empty array. * - #date_date_callbacks: Array of optional callbacks for the date element. * - #date_year_range: A description of the range of years to allow, like * '1900:2050', '-3:+3' or '2000:+3', where the first value describes the * earliest year and the second the latest year in the range. A year * in either position means that specific year. A +/- value describes a * dynamic value that is that many years earlier or later than the current * year at the time the form is displayed. Defaults to '1900:2050'. * - #date_increment: The increment to use for minutes and seconds, i.e. * '15' would show only :00, :15, :30 and :45. Defaults to 1 to show every * minute. * - #date_timezone: The local timezone to use when creating dates. Generally * this should be left empty and it will be set correctly for the user using * the form. Useful if the default value is empty to designate a desired * timezone for dates created in form processing. If a default date is * provided, this value will be ignored, the timezone in the default date * takes precedence. Defaults to the value returned by * drupal_get_user_timezone(). * * Example usage: * @code * $form = array( * '#type' => 'datelist', * '#default_value' => new DrupalDateTime('2000-01-01 00:00:00'), * '#date_part_order' => array('month', 'day', 'year', 'hour', 'minute', 'ampm'), * '#date_text_parts' => array('year'), * '#date_year_range' => '2010:2020', * '#date_increment' => 15, * ); * @endcode * * @param array $element * The form element whose value is being processed. * @param array $form_state * The current state of the form. */ function datetime_datelist_form_process($element, &$form_state) { // Load translated date part labels from the appropriate calendar plugin. $date_helper = new DateHelper(); // The value callback has populated the #value array. $date = !empty($element['#value']['object']) ? $element['#value']['object'] : NULL; // Set a fallback timezone. if ($date instanceOf DrupalDateTime) { $element['#date_timezone'] = $date->getTimezone()->getName(); } elseif (!empty($element['#timezone'])) { $element['#date_timezone'] = $element['#date_timezone']; } else { $element['#date_timezone'] = drupal_get_user_timezone(); } $element['#tree'] = TRUE; // Determine the order of the date elements. $order = !empty($element['#date_part_order']) ? $element['#date_part_order'] : array('year', 'month', 'day'); $text_parts = !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(); // Output multi-selector for date. foreach ($order as $part) { switch ($part) { case 'day': $options = $date_helper->days($element['#required']); $format = 'j'; $title = t('Day'); break; case 'month': $options = $date_helper->monthNamesAbbr($element['#required']); $format = 'n'; $title = t('Month'); break; case 'year': $range = datetime_range_years($element['#date_year_range'], $date); $options = $date_helper->years($range[0], $range[1], $element['#required']); $format = 'Y'; $title = t('Year'); break; case 'hour': $format = in_array('ampm', $element['#date_part_order']) ? 'g': 'G'; $options = $date_helper->hours($format, $element['#required']); $title = t('Hour'); break; case 'minute': $format = 'i'; $options = $date_helper->minutes($format, $element['#required'], $element['#date_increment']); $title = t('Minute'); break; case 'second': $format = 's'; $options = $date_helper->seconds($format, $element['#required'], $element['#date_increment']); $title = t('Second'); break; case 'ampm': $format = 'a'; $options = $date_helper->ampm($element['#required']); $title = t('AM/PM'); } $default = !empty($element['#value'][$part]) ? $element['#value'][$part] : ''; $value = $date instanceOf DrupalDateTime && !$date->hasErrors() ? $date->format($format) : $default; if (!empty($value) && $part != 'ampm') { $value = intval($value); } $element['#attributes']['title'] = $title; $element[$part] = array( '#type' => in_array($part, $text_parts) ? 'textfield' : 'select', '#title' => $title, '#title_display' => 'invisible', '#value' => $value, '#attributes' => $element['#attributes'], '#options' => $options, '#required' => $element['#required'], ); } // Allows custom callbacks to alter the element. if (!empty($element['#date_date_callbacks'])) { foreach ($element['#date_date_callbacks'] as $callback) { if (function_exists($callback)) { $callback($element, $form_state, $date); } } } return $element; } /** * Element value callback for datelist element. * * Validates the date type to adjust 12 hour time and prevent invalid dates. If * the date is valid, the date is set in the form. * * @param array $element * The element being processed. * @param array|false $input * * @param array $form_state * (optional) The current state of the form. Defaults to an empty array. * * @return array * */ function form_type_datelist_value($element, $input = FALSE, &$form_state = array()) { $parts = $element['#date_part_order']; $increment = $element['#date_increment']; $date = NULL; if ($input !== FALSE) { $return = $input; if (isset($input['ampm'])) { if ($input['ampm'] == 'pm' && $input['hour'] < 12) { $input['hour'] += 12; } elseif ($input['ampm'] == 'am' && $input['hour'] == 12) { $input['hour'] -= 12; } unset($input['ampm']); } $timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : NULL; $date = DrupalDateTime::createFromArray($input, $timezone); if ($date instanceOf DrupalDateTime && !$date->hasErrors()) { date_increment_round($date, $increment); } } else { $return = array_fill_keys($parts, ''); if (!empty($element['#default_value'])) { $date = $element['#default_value']; if ($date instanceOf DrupalDateTime && !$date->hasErrors()) { date_increment_round($date, $increment); foreach ($parts as $part) { switch ($part) { case 'day': $format = 'j'; break; case 'month': $format = 'n'; break; case 'year': $format = 'Y'; break; case 'hour': $format = in_array('ampm', $element['#date_part_order']) ? 'g': 'G'; break; case 'minute': $format = 'i'; break; case 'second': $format = 's'; break; case 'ampm': $format = 'a'; } $return[$part] = $date->format($format); } } } } $return['object'] = $date; return $return; } /** * Validation callback for a datelist element. * * If the date is valid, the date object created from the user input is set in * the form for use by the caller. The work of compiling the user input back * into a date object is handled by the value callback, so we can use it here. * We also have the raw input available for validation testing. * * @param array $element * The element being processed. * @param array $form_state * The current state of the form. */ function datetime_datelist_validate($element, &$form_state) { $input_exists = FALSE; $input = NestedArray::getValue($form_state['values'], $element['#parents'], $input_exists); if ($input_exists) { // If there's empty input and the field is not required, set it to empty. if (empty($input['year']) && empty($input['month']) && empty($input['day']) && !$element['#required']) { form_set_value($element, NULL, $form_state); } // If there's empty input and the field is required, set an error. elseif (empty($input['year']) && empty($input['month']) && empty($input['day']) && $element['#required']) { form_error($element, $form_state, t('The %field date is required.')); } else { // If the input is valid, set it. $date = $input['object']; if ($date instanceOf DrupalDateTime && !$date->hasErrors()) { form_set_value($element, $date, $form_state); } // If the input is invalid, set an error. else { form_error($element, $form_state, t('The %field date is invalid.')); } } } } /** * Rounds minutes and seconds to nearest requested value. * * @param $date * * @param $increment * * * @return * */ function date_increment_round(&$date, $increment) { // Round minutes and seconds, if necessary. if ($date instanceOf DrupalDateTime && $increment > 1) { $day = intval(date_format($date, 'j')); $hour = intval(date_format($date, 'H')); $second = intval(round(intval(date_format($date, 's')) / $increment) * $increment); $minute = intval(date_format($date, 'i')); if ($second == 60) { $minute += 1; $second = 0; } $minute = intval(round($minute / $increment) * $increment); if ($minute == 60) { $hour += 1; $minute = 0; } date_time_set($date, $hour, $minute, $second); if ($hour == 24) { $day += 1; $year = date_format($date, 'Y'); $month = date_format($date, 'n'); date_date_set($date, $year, $month, $day); } } return $date; } /** * Specifies the start and end year to use as a date range. * * Handles a string like -3:+3 or 2001:2010 to describe a dynamic range of * minimum and maximum years to use in a date selector. * * Centers the range around the current year, if any, but expands it far enough * so it will pick up the year value in the field in case the value in the field * is outside the initial range. * * @param string $string * A min and max year string like '-3:+1' or '2000:2010' or '2000:+3'. * @param object $date * (optional) A date object to test as a default value. Defaults to NULL. * * @return array * A numerically indexed array, containing the minimum and maximum year * described by this pattern. */ function datetime_range_years($string, $date = NULL) { $this_year = date_format(new DrupalDateTime(), 'Y'); list($min_year, $max_year) = explode(':', $string); // Valid patterns would be -5:+5, 0:+1, 2008:2010. $plus_pattern = '@[\+|\-][0-9]{1,4}@'; $year_pattern = '@^[0-9]{4}@'; if (!preg_match($year_pattern, $min_year, $matches)) { if (preg_match($plus_pattern, $min_year, $matches)) { $min_year = $this_year + $matches[0]; } else { $min_year = $this_year; } } if (!preg_match($year_pattern, $max_year, $matches)) { if (preg_match($plus_pattern, $max_year, $matches)) { $max_year = $this_year + $matches[0]; } else { $max_year = $this_year; } } // We expect the $min year to be less than the $max year. Some custom values // for -99:+99 might not obey that. if ($min_year > $max_year) { $temp = $max_year; $max_year = $min_year; $min_year = $temp; } // If there is a current value, stretch the range to include it. $value_year = $date instanceOf DrupalDateTime ? $date->format('Y') : ''; if (!empty($value_year)) { $min_year = min($value_year, $min_year); $max_year = max($value_year, $max_year); } return array($min_year, $max_year); } /** * Implements hook_form_BASE_FORM_ID_alter() for node forms. */ function datetime_form_node_form_alter(&$form, &$form_state, $form_id) { $format_type = datetime_default_format_type(); // Alter the 'Authored on' date to use datetime. $form['created']['#type'] = 'datetime'; $date_format = entity_load('date_format', 'html_date')->getPattern($format_type); $time_format = entity_load('date_format', 'html_time')->getPattern($format_type); $form['created']['#description'] = t('Format: %format. Leave blank to use the time of form submission.', array('%format' => datetime_format_example($date_format . ' ' . $time_format))); unset($form['created']['#maxlength']); } /** * Implements hook_node_prepare_form(). */ function datetime_node_prepare_form(NodeInterface $node, $form_display, $operation, array &$form_state) { // Prepare the 'Authored on' date to use datetime. $node->date = DrupalDateTime::createFromTimestamp($node->getCreatedTime()); }