Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,85 @@
# Schema for the configuration files of the Datetime module.
field.storage_settings.datetime:
type: mapping
label: 'Datetime settings'
mapping:
datetime_type:
type: string
label: 'Date type'
field.field_settings.datetime:
type: mapping
label: 'Datetime settings'
field.value.datetime:
type: mapping
label: 'Default value'
mapping:
default_date_type:
type: string
label: 'Default date type'
default_date:
type: string
label: 'Default date value'
field.formatter.settings.datetime_base:
type: mapping
mapping:
timezone_override:
type: string
label: 'Time zone override'
field.formatter.settings.datetime_default:
type: field.formatter.settings.datetime_base
label: 'Datetime default display format settings'
mapping:
format_type:
type: string
label: 'Date format'
field.formatter.settings.datetime_plain:
type: field.formatter.settings.datetime_base
label: 'Datetime plain display format settings'
field.formatter.settings.datetime_custom:
type: field.formatter.settings.datetime_base
label: 'Datetime custom display format settings'
mapping:
date_format:
type: string
label: 'Date/time format'
translatable: true
translation context: 'PHP date format'
field.formatter.settings.datetime_time_ago:
type: mapping
label: 'Datetime time ago display format settings'
mapping:
future_format:
type: string
label: 'Future format'
past_format:
type: string
label: 'Past format'
granularity:
type: integer
label: 'Granularity'
field.widget.settings.datetime_datelist:
type: mapping
label: 'Datetime select list display format settings'
mapping:
increment:
type: integer
label: 'Time increments'
date_order:
type: string
label: 'Date part order'
time_type:
type: string
label: 'Time type'
field.widget.settings.datetime_default:
type: mapping
label: 'Datetime default display format settings'

View file

@ -0,0 +1,8 @@
name: Datetime
type: module
description: Defines datetime form elements and a datetime field type.
package: Field types
version: VERSION
core: 8.x
dependencies:
- field

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Field hooks to implement a simple datetime field.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Defines the timezone that dates should be stored in.
*/
const DATETIME_STORAGE_TIMEZONE = 'UTC';
/**
* Defines the format that date and time should be stored in.
*/
const DATETIME_DATETIME_STORAGE_FORMAT = 'Y-m-d\TH:i:s';
/**
* Defines the format that dates should be stored in.
*/
const DATETIME_DATE_STORAGE_FORMAT = 'Y-m-d';
/**
* Implements hook_help().
*/
function datetime_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.datetime':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Datetime module provides a Date field that stores dates and times. It also provides the Form API elements <em>datetime</em> and <em>datelist</em> for use in programming modules. See the <a href=":field">Field module help</a> and the <a href=":field_ui">Field UI module help</a> pages for general information on fields and how to create and manage them. For more information, see the <a href=":datetime_do">online documentation for the Datetime module</a>.', array(':field' => \Drupal::url('help.page', array('name' => 'field')), ':field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? \Drupal::url('help.page', array('name' => 'field_ui')) : '#', ':datetime_do' => 'https://www.drupal.org/documentation/modules/datetime')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Managing and displaying date fields') . '</dt>';
$output .= '<dd>' . t('The <em>settings</em> and the <em>display</em> of the Date field can be configured separately. See the <a href=":field_ui">Field UI help</a> for more information on how to manage fields and their display.', array(':field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? \Drupal::url('help.page', array('name' => 'field_ui')) : '#')) . '</dd>';
$output .= '<dt>' . t('Displaying dates') . '</dt>';
$output .= '<dd>' . t('Dates can be displayed using the <em>Plain</em> or the <em>Default</em> formatter. The <em>Plain</em> formatter displays the date in the <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a> format. If you choose the <em>Default</em> formatter, you can choose a format from a predefined list that can be managed on the <a href=":date_format_list">Date and time formats</a> page.', array(':date_format_list' => \Drupal::url('entity.date_format.collection'))) . '</dd>';
$output .= '</dl>';
return $output;
}
}
/**
* 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);
}

View file

@ -0,0 +1,52 @@
<?php
/**
* @file
* Provides views data for the datetime module.
*/
use Drupal\field\FieldStorageConfigInterface;
/**
* Implements hook_field_views_data().
*/
function datetime_field_views_data(FieldStorageConfigInterface $field_storage) {
// @todo This code only covers configurable fields, handle base table fields
// in https://www.drupal.org/node/2489476.
$data = views_field_default_views_data($field_storage);
foreach ($data as $table_name => $table_data) {
// Set the 'datetime' filter type.
$data[$table_name][$field_storage->getName() . '_value']['filter']['id'] = 'datetime';
// Set the 'datetime' argument type.
$data[$table_name][$field_storage->getName() . '_value']['argument']['id'] = 'datetime';
// Create year, month, and day arguments.
$group = $data[$table_name][$field_storage->getName() . '_value']['group'];
$arguments = [
// Argument type => help text.
'year' => t('Date in the form of YYYY.'),
'month' => t('Date in the form of MM (01 - 12).'),
'day' => t('Date in the form of DD (01 - 31).'),
'week' => t('Date in the form of WW (01 - 53).'),
'year_month' => t('Date in the form of YYYYMM.'),
'full_date' => t('Date in the form of CCYYMMDD.'),
];
foreach ($arguments as $argument_type => $help_text) {
$data[$table_name][$field_storage->getName() . '_value_' . $argument_type] = [
'title' => $field_storage->getLabel() . ' (' . $argument_type . ')',
'help' => $help_text,
'argument' => [
'field' => $field_storage->getName() . '_value',
'id' => 'datetime_' . $argument_type,
],
'group' => $group,
];
}
// Set the 'datetime' sort handler.
$data[$table_name][$field_storage->getName() . '_value']['sort']['id'] = 'datetime';
}
return $data;
}

View file

@ -0,0 +1,85 @@
<?php
namespace Drupal\datetime;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedData;
/**
* A computed property for dates of date time field items.
*
* Required settings (below the definition's 'settings' key) are:
* - date source: The date property containing the to be computed date.
*/
class DateTimeComputed extends TypedData {
/**
* Cached computed date.
*
* @var \DateTime|null
*/
protected $date = NULL;
/**
* {@inheritdoc}
*/
public function __construct(DataDefinitionInterface $definition, $name = NULL, TypedDataInterface $parent = NULL) {
parent::__construct($definition, $name, $parent);
if (!$definition->getSetting('date source')) {
throw new \InvalidArgumentException("The definition's 'date source' key has to specify the name of the date property to be computed.");
}
}
/**
* {@inheritdoc}
*/
public function getValue($langcode = NULL) {
if ($this->date !== NULL) {
return $this->date;
}
/** @var \Drupal\Core\Field\FieldItemInterface $item */
$item = $this->getParent();
$value = $item->{($this->definition->getSetting('date source'))};
$datetime_type = $item->getFieldDefinition()->getSetting('datetime_type');
$storage_format = $datetime_type === DateTimeItem::DATETIME_TYPE_DATE ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT;
try {
$date = DrupalDateTime::createFromFormat($storage_format, $value, DATETIME_STORAGE_TIMEZONE);
if ($date instanceof DrupalDateTime && !$date->hasErrors()) {
$this->date = $date;
// If the format did not include an explicit time portion, then the
// time will be set from the current time instead. For consistency, we
// set the time to 12:00:00 UTC for date-only fields. This is used so
// that the local date portion is the same, across nearly all time
// zones.
// @see datetime_date_default_time()
// @see http://php.net/manual/en/datetime.createfromformat.php
// @todo Update comment and/or code per the chosen solution in
// https://www.drupal.org/node/2830094
if ($datetime_type === DateTimeItem::DATETIME_TYPE_DATE) {
$this->date->setTime(12, 0, 0);
}
}
}
catch (\Exception $e) {
// @todo Handle this.
}
return $this->date;
}
/**
* {@inheritdoc}
*/
public function setValue($value, $notify = TRUE) {
$this->date = $value;
// Notify the parent of any changes.
if ($notify && isset($this->parent)) {
$this->parent->onChange($this->name);
}
}
}

View file

@ -0,0 +1,102 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldFormatter;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'Custom' formatter for 'datetime' fields.
*
* @FieldFormatter(
* id = "datetime_custom",
* label = @Translation("Custom"),
* field_types = {
* "datetime"
* }
*)
*/
class DateTimeCustomFormatter extends DateTimeFormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return array(
'date_format' => DATETIME_DATETIME_STORAGE_FORMAT,
) + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
foreach ($items as $delta => $item) {
$output = '';
if (!empty($item->date)) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $date */
$date = $item->date;
if ($this->getFieldSetting('datetime_type') == 'date') {
// A date without time will pick up the current time, use the default.
datetime_date_default_time($date);
}
$this->setTimeZone($date);
$output = $this->formatDate($date);
}
$elements[$delta] = [
'#markup' => $output,
'#cache' => [
'contexts' => [
'timezone',
],
],
];
}
return $elements;
}
/**
* {@inheritdoc}
*/
protected function formatDate($date) {
$format = $this->getSetting('date_format');
$timezone = $this->getSetting('timezone_override');
return $this->dateFormatter->format($date->getTimestamp(), 'custom', $format, $timezone != '' ? $timezone : NULL);
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form['date_format'] = array(
'#type' => 'textfield',
'#title' => $this->t('Date/time format'),
'#description' => $this->t('See <a href="http://php.net/manual/function.date.php" target="_blank">the documentation for PHP date formats</a>.'),
'#default_value' => $this->getSetting('date_format'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$date = new DrupalDateTime();
$this->setTimeZone($date);
$summary[] = $date->format($this->getSetting('date_format'), $this->getFormatSettings());
return $summary;
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldFormatter;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'Default' formatter for 'datetime' fields.
*
* @FieldFormatter(
* id = "datetime_default",
* label = @Translation("Default"),
* field_types = {
* "datetime"
* }
* )
*/
class DateTimeDefaultFormatter extends DateTimeFormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return array(
'format_type' => 'medium',
) + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
foreach ($items as $delta => $item) {
$output = '';
$iso_date = '';
if ($item->date) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $date */
$date = $item->date;
if ($this->getFieldSetting('datetime_type') == 'date') {
// A date without time will pick up the current time, use the default.
datetime_date_default_time($date);
}
// Create the ISO date in Universal Time.
$iso_date = $date->format("Y-m-d\TH:i:s") . 'Z';
$this->setTimeZone($date);
$output = $this->formatDate($date);
}
// Display the date using theme datetime.
$elements[$delta] = array(
'#cache' => [
'contexts' => [
'timezone',
],
],
'#theme' => 'time',
'#text' => $output,
'#html' => FALSE,
'#attributes' => array(
'datetime' => $iso_date,
),
);
if (!empty($item->_attributes)) {
$elements[$delta]['#attributes'] += $item->_attributes;
// Unset field item attributes since they have been included in the
// formatter output and should not be rendered in the field template.
unset($item->_attributes);
}
}
return $elements;
}
/**
* {@inheritdoc}
*/
protected function formatDate($date) {
$format_type = $this->getSetting('format_type');
$timezone = $this->getSetting('timezone_override') ?: $date->getTimezone()->getName();
return $this->dateFormatter->format($date->getTimestamp(), $format_type, '', $timezone != '' ? $timezone : NULL);
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$time = new DrupalDateTime();
$format_types = $this->dateFormatStorage->loadMultiple();
$options = [];
foreach ($format_types as $type => $type_info) {
$format = $this->dateFormatter->format($time->format('U'), $type);
$options[$type] = $type_info->label() . ' (' . $format . ')';
}
$form['format_type'] = array(
'#type' => 'select',
'#title' => t('Date format'),
'#description' => t("Choose a format for displaying the date. Be sure to set a format appropriate for the field, i.e. omitting time for a field that only has a date."),
'#options' => $options,
'#default_value' => $this->getSetting('format_type'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$date = new DrupalDateTime();
$summary[] = t('Format: @display', array('@display' => $this->formatDate($date, $this->getFormatSettings())));
return $summary;
}
}

View file

@ -0,0 +1,170 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldFormatter;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for 'DateTime Field formatter' plugin implementations.
*/
abstract class DateTimeFormatterBase extends FormatterBase implements ContainerFactoryPluginInterface {
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* The date format entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $dateFormatStorage;
/**
* Constructs a new DateTimeDefaultFormatter.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the formatter is associated.
* @param array $settings
* The formatter settings.
* @param string $label
* The formatter label display setting.
* @param string $view_mode
* The view mode.
* @param array $third_party_settings
* Third party settings.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Drupal\Core\Entity\EntityStorageInterface $date_format_storage
* The date format entity storage.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, DateFormatterInterface $date_formatter, EntityStorageInterface $date_format_storage) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
$this->dateFormatter = $date_formatter;
$this->dateFormatStorage = $date_format_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['label'],
$configuration['view_mode'],
$configuration['third_party_settings'],
$container->get('date.formatter'),
$container->get('entity.manager')->getStorage('date_format')
);
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return array(
'timezone_override' => '',
) + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form['timezone_override'] = array(
'#type' => 'select',
'#title' => $this->t('Time zone override'),
'#description' => $this->t('The time zone selected here will always be used'),
'#options' => system_time_zones(TRUE),
'#default_value' => $this->getSetting('timezone_override'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
if ($override = $this->getSetting('timezone_override')) {
$summary[] = $this->t('Time zone: @timezone', array('@timezone' => $override));
}
return $summary;
}
/**
* Creates a formatted date value as a string.
*
* @param object $date
* A date object.
*
* @return string
* A formatted date string using the chosen format.
*/
abstract protected function formatDate($date);
/**
* Sets the proper time zone on a DrupalDateTime object for the current user.
*
* A DrupalDateTime object loaded from the database will have the UTC time
* zone applied to it. This method will apply the time zone for the current
* user, based on system and user settings.
*
* @see drupal_get_user_timezone()
*
* @param \Drupal\Core\Datetime\DrupalDateTime $date
* A DrupalDateTime object.
*/
protected function setTimeZone(DrupalDateTime $date) {
if ($this->getFieldSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
// A date without time has no timezone conversion.
$timezone = DATETIME_STORAGE_TIMEZONE;
}
else {
$timezone = drupal_get_user_timezone();
}
$date->setTimeZone(timezone_open($timezone));
}
/**
* Gets a settings array suitable for DrupalDateTime::format().
*
* @return array
* The settings array that can be passed to DrupalDateTime::format().
*/
protected function getFormatSettings() {
$settings = [];
if ($this->getSetting('timezone_override') != '') {
$settings['timezone'] = $this->getSetting('timezone_override');
}
return $settings;
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
/**
* Plugin implementation of the 'Plain' formatter for 'datetime' fields.
*
* @FieldFormatter(
* id = "datetime_plain",
* label = @Translation("Plain"),
* field_types = {
* "datetime"
* }
*)
*/
class DateTimePlainFormatter extends DateTimeFormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
foreach ($items as $delta => $item) {
$output = '';
if (!empty($item->date)) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $date */
$date = $item->date;
if ($this->getFieldSetting('datetime_type') == 'date') {
// A date without time will pick up the current time, use the default.
datetime_date_default_time($date);
}
$this->setTimeZone($date);
$output = $this->formatDate($date);
}
$elements[$delta] = [
'#cache' => [
'contexts' => [
'timezone',
],
],
'#markup' => $output,
];
}
return $elements;
}
/**
* {@inheritdoc}
*/
protected function formatDate($date) {
$format = $this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT;
$timezone = $this->getSetting('timezone_override');
return $this->dateFormatter->format($date->getTimestamp(), 'custom', $format, $timezone != '' ? $timezone : NULL);
}
}

View file

@ -0,0 +1,202 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldFormatter;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'Time ago' formatter for 'datetime' fields.
*
* @FieldFormatter(
* id = "datetime_time_ago",
* label = @Translation("Time ago"),
* field_types = {
* "datetime"
* }
* )
*/
class DateTimeTimeAgoFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* The current Request object.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* Constructs a DateTimeTimeAgoFormatter object.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the formatter is associated.
* @param array $settings
* The formatter settings.
* @param string $label
* The formatter label display setting.
* @param string $view_mode
* The view mode.
* @param array $third_party_settings
* Third party settings.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, DateFormatterInterface $date_formatter, Request $request) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
$this->dateFormatter = $date_formatter;
$this->request = $request;
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings = array(
'future_format' => '@interval hence',
'past_format' => '@interval ago',
'granularity' => 2,
) + parent::defaultSettings();
return $settings;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['label'],
$configuration['view_mode'],
$configuration['third_party_settings'],
$container->get('date.formatter'),
$container->get('request_stack')->getCurrentRequest()
);
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
foreach ($items as $delta => $item) {
$date = $item->date;
$output = [];
if (!empty($item->date)) {
if ($this->getFieldSetting('datetime_type') == 'date') {
// A date without time will pick up the current time, use the default.
datetime_date_default_time($date);
}
$output = $this->formatDate($date);
}
$elements[$delta] = $output;
}
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form['future_format'] = array(
'#type' => 'textfield',
'#title' => $this->t('Future format'),
'#default_value' => $this->getSetting('future_format'),
'#description' => $this->t('Use <em>@interval</em> where you want the formatted interval text to appear.'),
);
$form['past_format'] = array(
'#type' => 'textfield',
'#title' => $this->t('Past format'),
'#default_value' => $this->getSetting('past_format'),
'#description' => $this->t('Use <em>@interval</em> where you want the formatted interval text to appear.'),
);
$form['granularity'] = array(
'#type' => 'number',
'#title' => $this->t('Granularity'),
'#default_value' => $this->getSetting('granularity'),
'#description' => $this->t('How many time units should be shown in the formatted output.'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$future_date = new DrupalDateTime('1 year 1 month 1 week 1 day 1 hour 1 minute');
$past_date = new DrupalDateTime('-1 year -1 month -1 week -1 day -1 hour -1 minute');
$summary[] = t('Future date: %display', array('%display' => $this->formatDate($future_date)));
$summary[] = t('Past date: %display', array('%display' => $this->formatDate($past_date)));
return $summary;
}
/**
* Formats a date/time as a time interval.
*
* @param \Drupal\Core\Datetime\DrupalDateTime|object $date
* A date/time object.
*
* @return array
* The formatted date/time string using the past or future format setting.
*/
protected function formatDate(DrupalDateTime $date) {
$granularity = $this->getSetting('granularity');
$timestamp = $date->getTimestamp();
$options = [
'granularity' => $granularity,
'return_as_object' => TRUE,
];
if ($this->request->server->get('REQUEST_TIME') > $timestamp) {
$result = $this->dateFormatter->formatTimeDiffSince($timestamp, $options);
$build = [
'#markup' => SafeMarkup::format($this->getSetting('past_format'), ['@interval' => $result->getString()]),
];
}
else {
$result = $this->dateFormatter->formatTimeDiffUntil($timestamp, $options);
$build = [
'#markup' => SafeMarkup::format($this->getSetting('future_format'), ['@interval' => $result->getString()]),
];
}
CacheableMetadata::createFromObject($result)->applyTo($build);
return $build;
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldType;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\Form\FormStateInterface;
/**
* Represents a configurable entity datetime field.
*/
class DateTimeFieldItemList extends FieldItemList {
/**
* Defines the default value as now.
*/
const DEFAULT_VALUE_NOW = 'now';
/**
* Defines the default value as relative.
*/
const DEFAULT_VALUE_CUSTOM = 'relative';
/**
* {@inheritdoc}
*/
public function defaultValuesForm(array &$form, FormStateInterface $form_state) {
if (empty($this->getFieldDefinition()->getDefaultValueCallback())) {
$default_value = $this->getFieldDefinition()->getDefaultValueLiteral();
$element = array(
'#parents' => array('default_value_input'),
'default_date_type' => array(
'#type' => 'select',
'#title' => t('Default date'),
'#description' => t('Set a default value for this date.'),
'#default_value' => isset($default_value[0]['default_date_type']) ? $default_value[0]['default_date_type'] : '',
'#options' => array(
static::DEFAULT_VALUE_NOW => t('Current date'),
static::DEFAULT_VALUE_CUSTOM => t('Relative date'),
),
'#empty_value' => '',
),
'default_date' => array(
'#type' => 'textfield',
'#title' => t('Relative default value'),
'#description' => t("Describe a time by reference to the current day, like '+90 days' (90 days from the day the field is created) or '+1 Saturday' (the next Saturday). See <a href=\"http://php.net/manual/function.strtotime.php\">strtotime</a> for more details."),
'#default_value' => (isset($default_value[0]['default_date_type']) && $default_value[0]['default_date_type'] == static::DEFAULT_VALUE_CUSTOM) ? $default_value[0]['default_date'] : '',
'#states' => array(
'visible' => array(
':input[id="edit-default-value-input-default-date-type"]' => array('value' => static::DEFAULT_VALUE_CUSTOM),
)
)
)
);
return $element;
}
}
/**
* {@inheritdoc}
*/
public function defaultValuesFormValidate(array $element, array &$form, FormStateInterface $form_state) {
if ($form_state->getValue(['default_value_input', 'default_date_type']) == static::DEFAULT_VALUE_CUSTOM) {
$is_strtotime = @strtotime($form_state->getValue(array('default_value_input', 'default_date')));
if (!$is_strtotime) {
$form_state->setErrorByName('default_value_input][default_date', t('The relative date value entered is invalid.'));
}
}
}
/**
* {@inheritdoc}
*/
public function defaultValuesFormSubmit(array $element, array &$form, FormStateInterface $form_state) {
if ($form_state->getValue(array('default_value_input', 'default_date_type'))) {
if ($form_state->getValue(array('default_value_input', 'default_date_type')) == static::DEFAULT_VALUE_NOW) {
$form_state->setValueForElement($element['default_date'], static::DEFAULT_VALUE_NOW);
}
return array($form_state->getValue('default_value_input'));
}
return array();
}
/**
* {@inheritdoc}
*/
public static function processDefaultValue($default_value, FieldableEntityInterface $entity, FieldDefinitionInterface $definition) {
$default_value = parent::processDefaultValue($default_value, $entity, $definition);
if (isset($default_value[0]['default_date_type'])) {
if ($definition->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
// A default date only value should be in the format used for date
// storage but in the user's local timezone.
$date = new DrupalDateTime($default_value[0]['default_date'], drupal_get_user_timezone());
$format = DATETIME_DATE_STORAGE_FORMAT;
}
else {
// A default date+time value should be in the format and timezone used
// for date storage.
$date = new DrupalDateTime($default_value[0]['default_date'], DATETIME_STORAGE_TIMEZONE);
$format = DATETIME_DATETIME_STORAGE_FORMAT;
}
$value = $date->format($format);
// We only provide a default value for the first item, as do all fields.
// Otherwise, there is no way to clear out unwanted values on multiple value
// fields.
$default_value = array(
array(
'value' => $value,
'date' => $date,
)
);
}
return $default_value;
}
}

View file

@ -0,0 +1,138 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Field\FieldItemBase;
/**
* Plugin implementation of the 'datetime' field type.
*
* @FieldType(
* id = "datetime",
* label = @Translation("Date"),
* description = @Translation("Create and store date values."),
* default_widget = "datetime_default",
* default_formatter = "datetime_default",
* list_class = "\Drupal\datetime\Plugin\Field\FieldType\DateTimeFieldItemList"
* )
*/
class DateTimeItem extends FieldItemBase {
/**
* {@inheritdoc}
*/
public static function defaultStorageSettings() {
return array(
'datetime_type' => 'datetime',
) + parent::defaultStorageSettings();
}
/**
* Value for the 'datetime_type' setting: store only a date.
*/
const DATETIME_TYPE_DATE = 'date';
/**
* Value for the 'datetime_type' setting: store a date and time.
*/
const DATETIME_TYPE_DATETIME = 'datetime';
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['value'] = DataDefinition::create('datetime_iso8601')
->setLabel(t('Date value'))
->setRequired(TRUE);
$properties['date'] = DataDefinition::create('any')
->setLabel(t('Computed date'))
->setDescription(t('The computed DateTime object.'))
->setComputed(TRUE)
->setClass('\Drupal\datetime\DateTimeComputed')
->setSetting('date source', 'value');
return $properties;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return array(
'columns' => array(
'value' => array(
'description' => 'The date value.',
'type' => 'varchar',
'length' => 20,
),
),
'indexes' => array(
'value' => array('value'),
),
);
}
/**
* {@inheritdoc}
*/
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$element = array();
$element['datetime_type'] = array(
'#type' => 'select',
'#title' => t('Date type'),
'#description' => t('Choose the type of date to create.'),
'#default_value' => $this->getSetting('datetime_type'),
'#options' => array(
static::DATETIME_TYPE_DATETIME => t('Date and time'),
static::DATETIME_TYPE_DATE => t('Date only'),
),
'#disabled' => $has_data,
);
return $element;
}
/**
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$type = $field_definition->getSetting('datetime_type');
// Just pick a date in the past year. No guidance is provided by this Field
// type.
$timestamp = REQUEST_TIME - mt_rand(0, 86400 * 365);
if ($type == DateTimeItem::DATETIME_TYPE_DATE) {
$values['value'] = gmdate(DATETIME_DATE_STORAGE_FORMAT, $timestamp);
}
else {
$values['value'] = gmdate(DATETIME_DATETIME_STORAGE_FORMAT, $timestamp);
}
return $values;
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
$value = $this->get('value')->getValue();
return $value === NULL || $value === '';
}
/**
* {@inheritdoc}
*/
public function onChange($property_name, $notify = TRUE) {
// Enforce that the computed date is recalculated.
if ($property_name == 'value') {
$this->date = NULL;
}
parent::onChange($property_name, $notify);
}
}

View file

@ -0,0 +1,149 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'datetime_datelist' widget.
*
* @FieldWidget(
* id = "datetime_datelist",
* label = @Translation("Select list"),
* field_types = {
* "datetime"
* }
* )
*/
class DateTimeDatelistWidget extends DateTimeWidgetBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return array(
'increment' => '15',
'date_order' => 'YMD',
'time_type' => '24',
) + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element = parent::formElement($items, $delta, $element, $form, $form_state);
$date_order = $this->getSetting('date_order');
if ($this->getFieldSetting('datetime_type') == 'datetime') {
$time_type = $this->getSetting('time_type');
$increment = $this->getSetting('increment');
}
else {
$time_type = '';
$increment = '';
}
// Set up the date part order array.
switch ($date_order) {
case 'YMD':
$date_part_order = array('year', 'month', 'day');
break;
case 'MDY':
$date_part_order = array('month', 'day', 'year');
break;
case 'DMY':
$date_part_order = array('day', 'month', 'year');
break;
}
switch ($time_type) {
case '24':
$date_part_order = array_merge($date_part_order, array('hour', 'minute'));
break;
case '12':
$date_part_order = array_merge($date_part_order, array('hour', 'minute', 'ampm'));
break;
case 'none':
break;
}
$element['value'] = array(
'#type' => 'datelist',
'#date_increment' => $increment,
'#date_part_order' => $date_part_order,
) + $element['value'];
return $element;
}
/**
* {@inheritdoc}
*/
function settingsForm(array $form, FormStateInterface $form_state) {
$element = parent::settingsForm($form, $form_state);
$element['date_order'] = array(
'#type' => 'select',
'#title' => t('Date part order'),
'#default_value' => $this->getSetting('date_order'),
'#options' => array('MDY' => t('Month/Day/Year'), 'DMY' => t('Day/Month/Year'), 'YMD' => t('Year/Month/Day')),
);
if ($this->getFieldSetting('datetime_type') == 'datetime') {
$element['time_type'] = array(
'#type' => 'select',
'#title' => t('Time type'),
'#default_value' => $this->getSetting('time_type'),
'#options' => array('24' => t('24 hour time'), '12' => t('12 hour time')),
);
$element['increment'] = [
'#type' => 'select',
'#title' => t('Time increments'),
'#default_value' => $this->getSetting('increment'),
'#options' => [
1 => t('1 minute'),
5 => t('5 minute'),
10 => t('10 minute'),
15 => t('15 minute'),
30 => t('30 minute'),
],
];
}
else {
$element['time_type'] = array(
'#type' => 'hidden',
'#value' => 'none',
);
$element['increment'] = [
'#type' => 'hidden',
'#value' => $this->getSetting('increment'),
];
}
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = array();
$summary[] = t('Date part order: @order', array('@order' => $this->getSetting('date_order')));
if ($this->getFieldSetting('datetime_type') == 'datetime') {
$summary[] = t('Time type: @time_type', array('@time_type' => $this->getSetting('time_type')));
$summary[] = t('Time increments: @increment', array('@increment' => $this->getSetting('increment')));
}
return $summary;
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldWidget;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'datetime_default' widget.
*
* @FieldWidget(
* id = "datetime_default",
* label = @Translation("Date and time"),
* field_types = {
* "datetime"
* }
* )
*/
class DateTimeDefaultWidget extends DateTimeWidgetBase implements ContainerFactoryPluginInterface {
/**
* The date format storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $dateStorage;
/**
* {@inheritdoc}
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityStorageInterface $date_storage) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
$this->dateStorage = $date_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['third_party_settings'],
$container->get('entity.manager')->getStorage('date_format')
);
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element = parent::formElement($items, $delta, $element, $form, $form_state);
// Identify the type of date and time elements to use.
switch ($this->getFieldSetting('datetime_type')) {
case DateTimeItem::DATETIME_TYPE_DATE:
$date_type = 'date';
$time_type = 'none';
$date_format = $this->dateStorage->load('html_date')->getPattern();
$time_format = '';
break;
default:
$date_type = 'date';
$time_type = 'time';
$date_format = $this->dateStorage->load('html_date')->getPattern();
$time_format = $this->dateStorage->load('html_time')->getPattern();
break;
}
$element['value'] += array(
'#date_date_format' => $date_format,
'#date_date_element' => $date_type,
'#date_date_callbacks' => array(),
'#date_time_format' => $time_format,
'#date_time_element' => $time_type,
'#date_time_callbacks' => array(),
);
return $element;
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldWidget;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
/**
* Base class for the 'datetime_*' widgets.
*/
class DateTimeWidgetBase extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
// We are nesting some sub-elements inside the parent, so we need a wrapper.
// We also need to add another #title attribute at the top level for ease in
// identifying this item in error messages. We do not want to display this
// title because the actual title display is handled at a higher level by
// the Field module.
$element['#theme_wrappers'][] = 'datetime_wrapper';
$element['#attributes']['class'][] = 'container-inline';
$element['value'] = array(
'#type' => 'datetime',
'#default_value' => NULL,
'#date_increment' => 1,
'#date_timezone' => drupal_get_user_timezone(),
'#required' => $element['#required'],
);
if ($this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE) {
// A date-only field should have no timezone conversion performed, so
// use the same timezone as for storage.
$element['value']['#date_timezone'] = DATETIME_STORAGE_TIMEZONE;
}
if ($items[$delta]->date) {
$date = $items[$delta]->date;
// The date was created and verified during field_load(), so it is safe to
// use without further inspection.
if ($this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE) {
// A date without time will pick up the current time, use the default
// time.
datetime_date_default_time($date);
}
$date->setTimezone(new \DateTimeZone($element['value']['#date_timezone']));
$element['value']['#default_value'] = $date;
}
return $element;
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
// The widget form element type has transformed the value to a
// DrupalDateTime object at this point. We need to convert it back to the
// storage timezone and format.
foreach ($values as &$item) {
if (!empty($item['value']) && $item['value'] instanceof DrupalDateTime) {
$date = $item['value'];
switch ($this->getFieldSetting('datetime_type')) {
case DateTimeItem::DATETIME_TYPE_DATE:
// If this is a date-only field, set it to the default time so the
// timezone conversion can be reversed.
datetime_date_default_time($date);
$format = DATETIME_DATE_STORAGE_FORMAT;
break;
default:
$format = DATETIME_DATETIME_STORAGE_FORMAT;
break;
}
// Adjust the date for storage.
$date->setTimezone(new \DateTimezone(DATETIME_STORAGE_TIMEZONE));
$item['value'] = $date->format($format);
}
}
return $values;
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Drupal\datetime\Plugin\views\Argument;
use Drupal\views\Plugin\views\argument\Date as NumericDate;
/**
* Abstract argument handler for dates.
*
* Adds an option to set a default argument based on the current date.
*
* Definitions terms:
* - many to one: If true, the "many to one" helper will be used.
* - invalid input: A string to give to the user for obviously invalid input.
* This is deprecated in favor of argument validators.
*
* @see \Drupal\views\ManyTonOneHelper
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("datetime")
*/
class Date extends NumericDate {
/**
* {@inheritdoc}
*/
public function getDateField() {
// Return the real field, since it is already in string format.
return "$this->tableAlias.$this->realField";
}
/**
* {@inheritdoc}
*/
public function getDateFormat($format) {
// Pass in the string-field option.
return $this->query->getDateFormat($this->getDateField(), $format, TRUE);
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Drupal\datetime\Plugin\views\argument;
/**
* Argument handler for a day.
*
* @ViewsArgument("datetime_day")
*/
class DayDate extends Date {
/**
* {@inheritdoc}
*/
protected $argFormat = 'd';
}

View file

@ -0,0 +1,17 @@
<?php
namespace Drupal\datetime\Plugin\views\argument;
/**
* Argument handler for a full date (CCYYMMDD).
*
* @ViewsArgument("datetime_full_date")
*/
class FullDate extends Date {
/**
* {@inheritdoc}
*/
protected $argFormat = 'Ymd';
}

View file

@ -0,0 +1,17 @@
<?php
namespace Drupal\datetime\Plugin\views\argument;
/**
* Argument handler for a month.
*
* @ViewsArgument("datetime_month")
*/
class MonthDate extends Date {
/**
* {@inheritdoc}
*/
protected $argFormat = 'm';
}

View file

@ -0,0 +1,17 @@
<?php
namespace Drupal\datetime\Plugin\views\argument;
/**
* Argument handler for a week.
*
* @ViewsArgument("datetime_week")
*/
class WeekDate extends Date {
/**
* {@inheritdoc}
*/
protected $argFormat = 'W';
}

View file

@ -0,0 +1,17 @@
<?php
namespace Drupal\datetime\Plugin\views\argument;
/**
* Argument handler for a year.
*
* @ViewsArgument("datetime_year")
*/
class YearDate extends Date {
/**
* {@inheritdoc}
*/
protected $argFormat = 'Y';
}

View file

@ -0,0 +1,17 @@
<?php
namespace Drupal\datetime\Plugin\views\argument;
/**
* Argument handler for a year plus month (CCYYMM).
*
* @ViewsArgument("datetime_year_month")
*/
class YearMonthDate extends Date {
/**
* {@inheritdoc}
*/
protected $argFormat = 'Ym';
}

View file

@ -0,0 +1,126 @@
<?php
namespace Drupal\datetime\Plugin\views\filter;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\views\FieldAPIHandlerTrait;
use Drupal\views\Plugin\views\filter\Date as NumericDate;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Date/time views filter.
*
* Even thought dates are stored as strings, the numeric filter is extended
* because it provides more sensible operators.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("datetime")
*/
class Date extends NumericDate implements ContainerFactoryPluginInterface {
use FieldAPIHandlerTrait;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* Date format for SQL conversion.
*
* @var string
*
* @see \Drupal\views\Plugin\views\query\Sql::getDateFormat()
*/
protected $dateFormat = DATETIME_DATETIME_STORAGE_FORMAT;
/**
* The request stack used to determin current time.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Constructs a new Date handler.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack used to determine the current time.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, DateFormatterInterface $date_formatter, RequestStack $request_stack) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->dateFormatter = $date_formatter;
$this->requestStack = $request_stack;
// Date format depends on field storage format.
$definition = $this->getFieldStorageDefinition();
if ($definition->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
$this->dateFormat = DATETIME_DATE_STORAGE_FORMAT;
}
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('date.formatter'),
$container->get('request_stack')
);
}
/**
* Override parent method, which deals with dates as integers.
*/
protected function opBetween($field) {
$origin = ($this->value['type'] == 'offset') ? $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME') : 0;
$a = intval(strtotime($this->value['min'], $origin));
$b = intval(strtotime($this->value['max'], $origin));
// Formatting will vary on date storage.
// Convert to ISO format and format for query. UTC timezone is used since
// dates are stored in UTC.
$a = $this->query->getDateFormat("'" . $this->dateFormatter->format($a, 'custom', DATETIME_DATETIME_STORAGE_FORMAT, DATETIME_STORAGE_TIMEZONE) . "'", $this->dateFormat, TRUE);
$b = $this->query->getDateFormat("'" . $this->dateFormatter->format($b, 'custom', DATETIME_DATETIME_STORAGE_FORMAT, DATETIME_STORAGE_TIMEZONE) . "'", $this->dateFormat, TRUE);
// This is safe because we are manually scrubbing the values.
$operator = strtoupper($this->operator);
$field = $this->query->getDateFormat($field, $this->dateFormat, TRUE);
$this->query->addWhereExpression($this->options['group'], "$field $operator $a AND $b");
}
/**
* Override parent method, which deals with dates as integers.
*/
protected function opSimple($field) {
$origin = (!empty($this->value['type']) && $this->value['type'] == 'offset') ? $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME') : 0;
$value = intval(strtotime($this->value['value'], $origin));
// Convert to ISO. UTC is used since dates are stored in UTC.
$value = $this->query->getDateFormat("'" . $this->dateFormatter->format($value, 'custom', DATETIME_DATETIME_STORAGE_FORMAT, DATETIME_STORAGE_TIMEZONE) . "'", $this->dateFormat, TRUE);
// This is safe because we are manually scrubbing the value.
$field = $this->query->getDateFormat($field, $this->dateFormat, TRUE);
$this->query->addWhereExpression($this->options['group'], "$field $this->operator $value");
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\datetime\Plugin\views\sort;
use Drupal\views\Plugin\views\sort\Date as NumericDate;
/**
* Basic sort handler for datetime fields.
*
* This handler enables granularity, which is the ability to make dates
* equivalent based upon nearness.
*
* @ViewsSort("datetime")
*/
class Date extends NumericDate {
/**
* Override to account for dates stored as strings.
*/
public function getDateField() {
// Return the real field, since it is already in string format.
return "$this->tableAlias.$this->realField";
}
/**
* {@inheritdoc}
*
* Overridden in order to pass in the string date flag.
*/
public function getDateFormat($format) {
return $this->query->getDateFormat($this->getDateField(), $format, TRUE);
}
}

View file

@ -0,0 +1,182 @@
<?php
namespace Drupal\datetime\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\WebTestBase;
/**
* Provides a base class for testing Datetime field functionality.
*/
abstract class DateTestBase extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'entity_test', 'datetime', 'field_ui'];
/**
* An array of display options to pass to entity_get_display()
*
* @var array
*/
protected $displayOptions;
/**
* A field storage to use in this test class.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The field used in this test class.
*
* @var \Drupal\field\Entity\FieldConfig
*/
protected $field;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* An array of timezone extremes to test.
*
* @var string[]
*/
protected static $timezones = [
// UTC-12, no DST.
'Pacific/Kwajalein',
// UTC-11, no DST
'Pacific/Midway',
// UTC-7, no DST.
'America/Phoenix',
// UTC.
'UTC',
// UTC+5:30, no DST.
'Asia/Kolkata',
// UTC+12, no DST
'Pacific/Funafuti',
// UTC+13, no DST.
'Pacific/Tongatapu',
];
/**
* Returns the type of field to be tested.
*
* @return string
*/
abstract protected function getTestFieldType();
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser([
'access content',
'view test entity',
'administer entity_test content',
'administer entity_test form display',
'administer content types',
'administer node fields',
]);
$this->drupalLogin($web_user);
// Create a field with settings to validate.
$this->createField();
$this->dateFormatter = $this->container->get('date.formatter');
}
/**
* Creates a date test field.
*/
protected function createField() {
$field_name = Unicode::strtolower($this->randomMachineName());
$type = $this->getTestFieldType();
$widget_type = $formatter_type = $type . '_default';
$this->fieldStorage = FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => $type,
'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATE],
]);
$this->fieldStorage->save();
$this->field = FieldConfig::create([
'field_storage' => $this->fieldStorage,
'bundle' => 'entity_test',
'required' => TRUE,
]);
$this->field->save();
EntityFormDisplay::load('entity_test.entity_test.default')
->setComponent($field_name, ['type' => $widget_type])
->save();
$this->displayOptions = [
'type' => $formatter_type,
'label' => 'hidden',
'settings' => ['format_type' => 'medium'] + $this->defaultSettings,
];
EntityViewDisplay::create([
'targetEntityType' => $this->field->getTargetEntityTypeId(),
'bundle' => $this->field->getTargetBundle(),
'mode' => 'full',
'status' => TRUE,
])->setComponent($field_name, $this->displayOptions)
->save();
}
/**
* Renders a entity_test and sets the output in the internal browser.
*
* @param int $id
* The entity_test ID to render.
* @param string $view_mode
* (optional) The view mode to use for rendering. Defaults to 'full'.
* @param bool $reset
* (optional) Whether to reset the entity_test controller cache. Defaults to
* TRUE to simplify testing.
*/
protected function renderTestEntity($id, $view_mode = 'full', $reset = TRUE) {
if ($reset) {
$this->container->get('entity_type.manager')->getStorage('entity_test')->resetCache([$id]);
}
$entity = EntityTest::load($id);
$display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
$build = $display->build($entity);
$output = $this->container->get('renderer')->renderRoot($build);
$this->setRawContent($output);
$this->verbose($output);
}
/**
* Sets the site timezone to a given timezone.
*
* @param string $timezone
* The timezone identifier to set.
*/
protected function setSiteTimezone($timezone) {
// Set an explicit site timezone, and disallow per-user timezones.
$this->config('system.date')
->set('timezone.user.configurable', 0)
->set('timezone.default', $timezone)
->save();
}
}

View file

@ -0,0 +1,822 @@
<?php
namespace Drupal\datetime\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\node\Entity\Node;
/**
* Tests Datetime field functionality.
*
* @group datetime
*/
class DateTimeFieldTest extends DateTestBase {
/**
* The default display settings to use for the formatters.
*
* @var array
*/
protected $defaultSettings = ['timezone_override' => ''];
/**
* {@inheritdoc}
*/
protected function getTestFieldType() {
return 'datetime';
}
/**
* Tests date field functionality.
*/
function testDateField() {
$field_name = $this->fieldStorage->getName();
// Loop through defined timezones to test that date-only fields work at the
// extremes.
foreach (static::$timezones as $timezone) {
$this->setSiteTimezone($timezone);
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value][date]", '', 'Date element found.');
$this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]/h4[contains(@class, "js-form-required")]', TRUE, 'Required markup found');
$this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Time element not found.');
// Build up a date in the UTC timezone. Note that using this will also
// mimic the user in a different timezone simply entering '2012-12-31' via
// the UI.
$value = '2012-12-31 00:00:00';
$date = new DrupalDateTime($value, DATETIME_STORAGE_TIMEZONE);
// Submit a valid date and ensure it is accepted.
$date_format = DateFormat::load('html_date')->getPattern();
$time_format = DateFormat::load('html_time')->getPattern();
$edit = array(
"{$field_name}[0][value][date]" => $date->format($date_format),
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
$this->assertRaw($date->format($date_format));
$this->assertNoRaw($date->format($time_format));
// Verify the date doesn't change if using a timezone that is UTC+12 when
// the entity is edited through the form.
$entity = EntityTest::load($id);
$this->assertEqual('2012-12-31', $entity->{$field_name}->value);
$this->drupalGet('entity_test/manage/' . $id . '/edit');
$this->drupalPostForm(NULL, [], t('Save'));
$this->drupalGet('entity_test/manage/' . $id . '/edit');
$this->drupalPostForm(NULL, [], t('Save'));
$this->drupalGet('entity_test/manage/' . $id . '/edit');
$this->drupalPostForm(NULL, [], t('Save'));
$entity = EntityTest::load($id);
$this->assertEqual('2012-12-31', $entity->{$field_name}->value);
// Reset display options since these get changed below.
$this->displayOptions = array(
'type' => 'datetime_default',
'label' => 'hidden',
'settings' => array('format_type' => 'medium') + $this->defaultSettings,
);
// Verify that the date is output according to the formatter settings.
$options = array(
'format_type' => array('short', 'medium', 'long'),
);
// Formats that display a time component for date-only fields will display
// the default time, so that is applied before calculating the expected
// value.
datetime_date_default_time($date);
foreach ($options as $setting => $values) {
foreach ($values as $new_value) {
// Update the entity display settings.
$this->displayOptions['settings'] = array($setting => $new_value) + $this->defaultSettings;
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)
->save();
$this->renderTestEntity($id);
switch ($setting) {
case 'format_type':
// Verify that a date is displayed. Since this is a date-only
// field, it is expected to display the time as 00:00:00.
$expected = format_date($date->getTimestamp(), $new_value, '', DATETIME_STORAGE_TIMEZONE);
$expected_iso = format_date($date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DATETIME_STORAGE_TIMEZONE);
$this->renderTestEntity($id);
$this->assertFieldByXPath('//time[@datetime="' . $expected_iso . '"]', $expected, SafeMarkup::format('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', array('%value' => $new_value, '%expected' => $expected, '%expected_iso' => $expected_iso)));
break;
}
}
}
// Verify that the plain formatter works.
$this->displayOptions['type'] = 'datetime_plain';
$this->displayOptions['settings'] = $this->defaultSettings;
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)
->save();
$expected = $date->format(DATETIME_DATE_STORAGE_FORMAT);
$this->renderTestEntity($id);
$this->assertText($expected, SafeMarkup::format('Formatted date field using plain format displayed as %expected.', array('%expected' => $expected)));
// Verify that the 'datetime_custom' formatter works.
$this->displayOptions['type'] = 'datetime_custom';
$this->displayOptions['settings'] = array('date_format' => 'm/d/Y') + $this->defaultSettings;
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)
->save();
$expected = $date->format($this->displayOptions['settings']['date_format']);
$this->renderTestEntity($id);
$this->assertText($expected, SafeMarkup::format('Formatted date field using datetime_custom format displayed as %expected.', array('%expected' => $expected)));
// Verify that the 'datetime_time_ago' formatter works for intervals in the
// past. First update the test entity so that the date difference always
// has the same interval. Since the database always stores UTC, and the
// interval will use this, force the test date to use UTC and not the local
// or user timezome.
$timestamp = REQUEST_TIME - 87654321;
$entity = EntityTest::load($id);
$field_name = $this->fieldStorage->getName();
$date = DrupalDateTime::createFromTimestamp($timestamp, 'UTC');
$entity->{$field_name}->value = $date->format($date_format);
$entity->save();
$this->displayOptions['type'] = 'datetime_time_ago';
$this->displayOptions['settings'] = array(
'future_format' => '@interval in the future',
'past_format' => '@interval in the past',
'granularity' => 3,
);
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)
->save();
$expected = SafeMarkup::format($this->displayOptions['settings']['past_format'], [
'@interval' => $this->dateFormatter->formatTimeDiffSince($timestamp, ['granularity' => $this->displayOptions['settings']['granularity']])
]);
$this->renderTestEntity($id);
$this->assertText($expected, SafeMarkup::format('Formatted date field using datetime_time_ago format displayed as %expected.', array('%expected' => $expected)));
// Verify that the 'datetime_time_ago' formatter works for intervals in the
// future. First update the test entity so that the date difference always
// has the same interval. Since the database always stores UTC, and the
// interval will use this, force the test date to use UTC and not the local
// or user timezome.
$timestamp = REQUEST_TIME + 87654321;
$entity = EntityTest::load($id);
$field_name = $this->fieldStorage->getName();
$date = DrupalDateTime::createFromTimestamp($timestamp, 'UTC');
$entity->{$field_name}->value = $date->format($date_format);
$entity->save();
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)
->save();
$expected = SafeMarkup::format($this->displayOptions['settings']['future_format'], [
'@interval' => $this->dateFormatter->formatTimeDiffUntil($timestamp, ['granularity' => $this->displayOptions['settings']['granularity']])
]);
$this->renderTestEntity($id);
$this->assertText($expected, SafeMarkup::format('Formatted date field using datetime_time_ago format displayed as %expected.', array('%expected' => $expected)));
}
}
/**
* Tests date and time field.
*/
function testDatetimeField() {
$field_name = $this->fieldStorage->getName();
// Change the field to a datetime field.
$this->fieldStorage->setSetting('datetime_type', 'datetime');
$this->fieldStorage->save();
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value][date]", '', 'Date element found.');
$this->assertFieldByName("{$field_name}[0][value][time]", '', 'Time element found.');
// Build up a date in the UTC timezone.
$value = '2012-12-31 00:00:00';
$date = new DrupalDateTime($value, 'UTC');
// Update the timezone to the system default.
$date->setTimezone(timezone_open(drupal_get_user_timezone()));
// Submit a valid date and ensure it is accepted.
$date_format = DateFormat::load('html_date')->getPattern();
$time_format = DateFormat::load('html_time')->getPattern();
$edit = array(
"{$field_name}[0][value][date]" => $date->format($date_format),
"{$field_name}[0][value][time]" => $date->format($time_format),
);
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
$this->assertRaw($date->format($date_format));
$this->assertRaw($date->format($time_format));
// Verify that the date is output according to the formatter settings.
$options = array(
'format_type' => array('short', 'medium', 'long'),
);
foreach ($options as $setting => $values) {
foreach ($values as $new_value) {
// Update the entity display settings.
$this->displayOptions['settings'] = array($setting => $new_value) + $this->defaultSettings;
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)
->save();
$this->renderTestEntity($id);
switch ($setting) {
case 'format_type':
// Verify that a date is displayed.
$expected = format_date($date->getTimestamp(), $new_value);
$expected_iso = format_date($date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
$this->renderTestEntity($id);
$this->assertFieldByXPath('//time[@datetime="' . $expected_iso . '"]', $expected, SafeMarkup::format('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', array('%value' => $new_value, '%expected' => $expected, '%expected_iso' => $expected_iso)));
break;
}
}
}
// Verify that the plain formatter works.
$this->displayOptions['type'] = 'datetime_plain';
$this->displayOptions['settings'] = $this->defaultSettings;
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)
->save();
$expected = $date->format(DATETIME_DATETIME_STORAGE_FORMAT);
$this->renderTestEntity($id);
$this->assertText($expected, SafeMarkup::format('Formatted date field using plain format displayed as %expected.', array('%expected' => $expected)));
// Verify that the 'datetime_custom' formatter works.
$this->displayOptions['type'] = 'datetime_custom';
$this->displayOptions['settings'] = array('date_format' => 'm/d/Y g:i:s A') + $this->defaultSettings;
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)
->save();
$expected = $date->format($this->displayOptions['settings']['date_format']);
$this->renderTestEntity($id);
$this->assertText($expected, SafeMarkup::format('Formatted date field using datetime_custom format displayed as %expected.', array('%expected' => $expected)));
// Verify that the 'timezone_override' setting works.
$this->displayOptions['type'] = 'datetime_custom';
$this->displayOptions['settings'] = array('date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York') + $this->defaultSettings;
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)
->save();
$expected = $date->format($this->displayOptions['settings']['date_format'], array('timezone' => 'America/New_York'));
$this->renderTestEntity($id);
$this->assertText($expected, SafeMarkup::format('Formatted date field using datetime_custom format displayed as %expected.', array('%expected' => $expected)));
// Verify that the 'datetime_time_ago' formatter works for intervals in the
// past. First update the test entity so that the date difference always
// has the same interval. Since the database always stores UTC, and the
// interval will use this, force the test date to use UTC and not the local
// or user timezome.
$timestamp = REQUEST_TIME - 87654321;
$entity = EntityTest::load($id);
$field_name = $this->fieldStorage->getName();
$date = DrupalDateTime::createFromTimestamp($timestamp, 'UTC');
$entity->{$field_name}->value = $date->format(DATETIME_DATETIME_STORAGE_FORMAT);
$entity->save();
$this->displayOptions['type'] = 'datetime_time_ago';
$this->displayOptions['settings'] = array(
'future_format' => '@interval from now',
'past_format' => '@interval earlier',
'granularity' => 3,
);
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)
->save();
$expected = SafeMarkup::format($this->displayOptions['settings']['past_format'], [
'@interval' => $this->dateFormatter->formatTimeDiffSince($timestamp, ['granularity' => $this->displayOptions['settings']['granularity']])
]);
$this->renderTestEntity($id);
$this->assertText($expected, SafeMarkup::format('Formatted date field using datetime_time_ago format displayed as %expected.', array('%expected' => $expected)));
// Verify that the 'datetime_time_ago' formatter works for intervals in the
// future. First update the test entity so that the date difference always
// has the same interval. Since the database always stores UTC, and the
// interval will use this, force the test date to use UTC and not the local
// or user timezome.
$timestamp = REQUEST_TIME + 87654321;
$entity = EntityTest::load($id);
$field_name = $this->fieldStorage->getName();
$date = DrupalDateTime::createFromTimestamp($timestamp, 'UTC');
$entity->{$field_name}->value = $date->format(DATETIME_DATETIME_STORAGE_FORMAT);
$entity->save();
entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
->setComponent($field_name, $this->displayOptions)
->save();
$expected = SafeMarkup::format($this->displayOptions['settings']['future_format'], [
'@interval' => $this->dateFormatter->formatTimeDiffUntil($timestamp, ['granularity' => $this->displayOptions['settings']['granularity']])
]);
$this->renderTestEntity($id);
$this->assertText($expected, SafeMarkup::format('Formatted date field using datetime_time_ago format displayed as %expected.', array('%expected' => $expected)));
}
/**
* Tests Date List Widget functionality.
*/
function testDatelistWidget() {
$field_name = $this->fieldStorage->getName();
// Ensure field is set to a date only field.
$this->fieldStorage->setSetting('datetime_type', 'date');
$this->fieldStorage->save();
// Change the widget to a datelist widget.
entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
->setComponent($field_name, array(
'type' => 'datetime_datelist',
'settings' => array(
'date_order' => 'YMD',
),
))
->save();
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Display creation form.
$this->drupalGet('entity_test/add');
// Assert that Hour and Minute Elements do not appear on Date Only
$this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.');
$this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.');
// Go to the form display page to assert that increment option does not appear on Date Only
$fieldEditUrl = 'entity_test/structure/entity_test/form-display';
$this->drupalGet($fieldEditUrl);
// Click on the widget settings button to open the widget settings form.
$this->drupalPostAjaxForm(NULL, array(), $field_name . "_settings_edit");
$xpathIncr = "//select[starts-with(@id, \"edit-fields-$field_name-settings-edit-form-settings-increment\")]";
$this->assertNoFieldByXPath($xpathIncr, NULL, 'Increment element not found for Date Only.');
// Change the field to a datetime field.
$this->fieldStorage->setSetting('datetime_type', 'datetime');
$this->fieldStorage->save();
// Change the widget to a datelist widget.
entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
->setComponent($field_name, array(
'type' => 'datetime_datelist',
'settings' => array(
'increment' => 1,
'date_order' => 'YMD',
'time_type' => '12',
),
))
->save();
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Go to the form display page to assert that increment option does appear on Date Time
$fieldEditUrl = 'entity_test/structure/entity_test/form-display';
$this->drupalGet($fieldEditUrl);
// Click on the widget settings button to open the widget settings form.
$this->drupalPostAjaxForm(NULL, array(), $field_name . "_settings_edit");
$this->assertFieldByXPath($xpathIncr, NULL, 'Increment element found for Date and time.');
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-year\"]", NULL, 'Year element found.');
$this->assertOptionSelected("edit-$field_name-0-value-year", '', 'No year selected.');
$this->assertOptionByText("edit-$field_name-0-value-year", t('Year'));
$this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-month\"]", NULL, 'Month element found.');
$this->assertOptionSelected("edit-$field_name-0-value-month", '', 'No month selected.');
$this->assertOptionByText("edit-$field_name-0-value-month", t('Month'));
$this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-day\"]", NULL, 'Day element found.');
$this->assertOptionSelected("edit-$field_name-0-value-day", '', 'No day selected.');
$this->assertOptionByText("edit-$field_name-0-value-day", t('Day'));
$this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element found.');
$this->assertOptionSelected("edit-$field_name-0-value-hour", '', 'No hour selected.');
$this->assertOptionByText("edit-$field_name-0-value-hour", t('Hour'));
$this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element found.');
$this->assertOptionSelected("edit-$field_name-0-value-minute", '', 'No minute selected.');
$this->assertOptionByText("edit-$field_name-0-value-minute", t('Minute'));
$this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-second\"]", NULL, 'Second element not found.');
$this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-ampm\"]", NULL, 'AMPM element found.');
$this->assertOptionSelected("edit-$field_name-0-value-ampm", '', 'No ampm selected.');
$this->assertOptionByText("edit-$field_name-0-value-ampm", t('AM/PM'));
// Submit a valid date and ensure it is accepted.
$date_value = array('year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 5, 'minute' => 15);
$edit = array();
// Add the ampm indicator since we are testing 12 hour time.
$date_value['ampm'] = 'am';
foreach ($date_value as $part => $value) {
$edit["{$field_name}[0][value][$part]"] = $value;
}
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
$this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.');
$this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.');
$this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.');
$this->assertOptionSelected("edit-$field_name-0-value-hour", '5', 'Correct hour selected.');
$this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.');
$this->assertOptionSelected("edit-$field_name-0-value-ampm", 'am', 'Correct ampm selected.');
// Test the widget using increment other than 1 and 24 hour mode.
entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
->setComponent($field_name, array(
'type' => 'datetime_datelist',
'settings' => array(
'increment' => 15,
'date_order' => 'YMD',
'time_type' => '24',
),
))
->save();
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Display creation form.
$this->drupalGet('entity_test/add');
// Other elements are unaffected by the changed settings.
$this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element found.');
$this->assertOptionSelected("edit-$field_name-0-value-hour", '', 'No hour selected.');
$this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-ampm\"]", NULL, 'AMPM element not found.');
// Submit a valid date and ensure it is accepted.
$date_value = array('year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 17, 'minute' => 15);
$edit = array();
foreach ($date_value as $part => $value) {
$edit["{$field_name}[0][value][$part]"] = $value;
}
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
$this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.');
$this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.');
$this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.');
$this->assertOptionSelected("edit-$field_name-0-value-hour", '17', 'Correct hour selected.');
$this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.');
// Test the widget for partial completion of fields.
entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
->setComponent($field_name, array(
'type' => 'datetime_datelist',
'settings' => array(
'increment' => 1,
'date_order' => 'YMD',
'time_type' => '24',
),
))
->save();
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Test the widget for validation notifications.
foreach ($this->datelistDataProvider() as $data) {
list($date_value, $expected) = $data;
// Display creation form.
$this->drupalGet('entity_test/add');
// Submit a partial date and ensure and error message is provided.
$edit = array();
foreach ($date_value as $part => $value) {
$edit["{$field_name}[0][value][$part]"] = $value;
}
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertResponse(200);
foreach ($expected as $expected_text) {
$this->assertText(t($expected_text));
}
}
// Test the widget for complete input with zeros as part of selections.
$this->drupalGet('entity_test/add');
$date_value = array('year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0');
$edit = array();
foreach ($date_value as $part => $value) {
$edit["{$field_name}[0][value][$part]"] = $value;
}
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertResponse(200);
preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
// Test the widget to ensure zeros are not deselected on validation.
$this->drupalGet('entity_test/add');
$date_value = array('year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => '0');
$edit = array();
foreach ($date_value as $part => $value) {
$edit["{$field_name}[0][value][$part]"] = $value;
}
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertResponse(200);
$this->assertOptionSelected("edit-$field_name-0-value-minute", '0', 'Correct minute selected.');
}
/**
* The data provider for testing the validation of the datelist widget.
*
* @return array
* An array of datelist input permutations to test.
*/
protected function datelistDataProvider() {
return [
// Year only selected, validation error on Month, Day, Hour, Minute.
[['year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], [
'A value must be selected for month.',
'A value must be selected for day.',
'A value must be selected for hour.',
'A value must be selected for minute.',
]],
// Year and Month selected, validation error on Day, Hour, Minute.
[['year' => 2012, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => ''], [
'A value must be selected for day.',
'A value must be selected for hour.',
'A value must be selected for minute.',
]],
// Year, Month and Day selected, validation error on Hour, Minute.
[['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => ''], [
'A value must be selected for hour.',
'A value must be selected for minute.',
]],
// Year, Month, Day and Hour selected, validation error on Minute only.
[['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => ''], [
'A value must be selected for minute.',
]],
];
}
/**
* Test default value functionality.
*/
function testDefaultValue() {
// Create a test content type.
$this->drupalCreateContentType(array('type' => 'date_content'));
// Create a field storage with settings to validate.
$field_name = Unicode::strtolower($this->randomMachineName());
$field_storage = FieldStorageConfig::create(array(
'field_name' => $field_name,
'entity_type' => 'node',
'type' => 'datetime',
'settings' => array('datetime_type' => 'date'),
));
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'date_content',
]);
$field->save();
// Loop through defined timezones to test that date-only defaults work at
// the extremes.
foreach (static::$timezones as $timezone) {
$this->setSiteTimezone($timezone);
// Set now as default_value.
$field_edit = array(
'default_value_input[default_date_type]' => 'now',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
// Check that default value is selected in default value form.
$this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
$this->assertOptionSelected('edit-default-value-input-default-date-type', 'now', 'The default value is selected in instance settings page');
$this->assertFieldByName('default_value_input[default_date]', '', 'The relative default value is empty in instance settings page');
// Check if default_date has been stored successfully.
$config_entity = $this->config('field.field.node.date_content.' . $field_name)
->get();
$this->assertEqual($config_entity['default_value'][0], array(
'default_date_type' => 'now',
'default_date' => 'now',
), 'Default value has been stored successfully');
// Clear field cache in order to avoid stale cache values.
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Create a new node to check that datetime field default value is today.
$new_node = Node::create(['type' => 'date_content']);
$expected_date = new DrupalDateTime('now', drupal_get_user_timezone());
$this->assertEqual($new_node->get($field_name)
->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT));
// Set an invalid relative default_value to test validation.
$field_edit = array(
'default_value_input[default_date_type]' => 'relative',
'default_value_input[default_date]' => 'invalid date',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
$this->assertText('The relative date value entered is invalid.');
// Set a relative default_value.
$field_edit = array(
'default_value_input[default_date_type]' => 'relative',
'default_value_input[default_date]' => '+90 days',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
// Check that default value is selected in default value form.
$this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
$this->assertOptionSelected('edit-default-value-input-default-date-type', 'relative', 'The default value is selected in instance settings page');
$this->assertFieldByName('default_value_input[default_date]', '+90 days', 'The relative default value is displayed in instance settings page');
// Check if default_date has been stored successfully.
$config_entity = $this->config('field.field.node.date_content.' . $field_name)
->get();
$this->assertEqual($config_entity['default_value'][0], array(
'default_date_type' => 'relative',
'default_date' => '+90 days',
), 'Default value has been stored successfully');
// Clear field cache in order to avoid stale cache values.
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Create a new node to check that datetime field default value is +90
// days.
$new_node = Node::create(['type' => 'date_content']);
$expected_date = new DrupalDateTime('+90 days', drupal_get_user_timezone());
$this->assertEqual($new_node->get($field_name)
->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT));
// Remove default value.
$field_edit = array(
'default_value_input[default_date_type]' => '',
);
$this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
// Check that default value is selected in default value form.
$this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
$this->assertOptionSelected('edit-default-value-input-default-date-type', '', 'The default value is selected in instance settings page');
$this->assertFieldByName('default_value_input[default_date]', '', 'The relative default value is empty in instance settings page');
// Check if default_date has been stored successfully.
$config_entity = $this->config('field.field.node.date_content.' . $field_name)
->get();
$this->assertTrue(empty($config_entity['default_value']), 'Empty default value has been stored successfully');
// Clear field cache in order to avoid stale cache values.
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Create a new node to check that datetime field default value is not
// set.
$new_node = Node::create(['type' => 'date_content']);
$this->assertNull($new_node->get($field_name)->value, 'Default value is not set');
}
}
/**
* Test that invalid values are caught and marked as invalid.
*/
function testInvalidField() {
// Change the field to a datetime field.
$this->fieldStorage->setSetting('datetime_type', 'datetime');
$this->fieldStorage->save();
$field_name = $this->fieldStorage->getName();
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value][date]", '', 'Date element found.');
$this->assertFieldByName("{$field_name}[0][value][time]", '', 'Time element found.');
// Submit invalid dates and ensure they is not accepted.
$date_value = '';
$edit = array(
"{$field_name}[0][value][date]" => $date_value,
"{$field_name}[0][value][time]" => '12:00:00',
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText('date is invalid', 'Empty date value has been caught.');
$date_value = 'aaaa-12-01';
$edit = array(
"{$field_name}[0][value][date]" => $date_value,
"{$field_name}[0][value][time]" => '00:00:00',
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText('date is invalid', format_string('Invalid year value %date has been caught.', array('%date' => $date_value)));
$date_value = '2012-75-01';
$edit = array(
"{$field_name}[0][value][date]" => $date_value,
"{$field_name}[0][value][time]" => '00:00:00',
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText('date is invalid', format_string('Invalid month value %date has been caught.', array('%date' => $date_value)));
$date_value = '2012-12-99';
$edit = array(
"{$field_name}[0][value][date]" => $date_value,
"{$field_name}[0][value][time]" => '00:00:00',
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText('date is invalid', format_string('Invalid day value %date has been caught.', array('%date' => $date_value)));
$date_value = '2012-12-01';
$time_value = '';
$edit = array(
"{$field_name}[0][value][date]" => $date_value,
"{$field_name}[0][value][time]" => $time_value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText('date is invalid', 'Empty time value has been caught.');
$date_value = '2012-12-01';
$time_value = '49:00:00';
$edit = array(
"{$field_name}[0][value][date]" => $date_value,
"{$field_name}[0][value][time]" => $time_value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText('date is invalid', format_string('Invalid hour value %time has been caught.', array('%time' => $time_value)));
$date_value = '2012-12-01';
$time_value = '12:99:00';
$edit = array(
"{$field_name}[0][value][date]" => $date_value,
"{$field_name}[0][value][time]" => $time_value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText('date is invalid', format_string('Invalid minute value %time has been caught.', array('%time' => $time_value)));
$date_value = '2012-12-01';
$time_value = '12:15:99';
$edit = array(
"{$field_name}[0][value][date]" => $date_value,
"{$field_name}[0][value][time]" => $time_value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText('date is invalid', format_string('Invalid second value %time has been caught.', array('%time' => $time_value)));
}
/**
* Tests that 'Date' field storage setting form is disabled if field has data.
*/
public function testDateStorageSettings() {
// Create a test content type.
$this->drupalCreateContentType(['type' => 'date_content']);
// Create a field storage with settings to validate.
$field_name = Unicode::strtolower($this->randomMachineName());
$field_storage = FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => 'node',
'type' => 'datetime',
'settings' => [
'datetime_type' => 'date',
],
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'field_name' => $field_name,
'bundle' => 'date_content',
]);
$field->save();
entity_get_form_display('node', 'date_content', 'default')
->setComponent($field_name, [
'type' => 'datetime_default',
])
->save();
$edit = [
'title[0][value]' => $this->randomString(),
'body[0][value]' => $this->randomString(),
$field_name . '[0][value][date]' => '2016-04-01',
];
$this->drupalPostForm('node/add/date_content', $edit, t('Save'));
$this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name . '/storage');
$result = $this->xpath("//*[@id='edit-settings-datetime-type' and contains(@disabled, 'disabled')]");
$this->assertEqual(count($result), 1, "Changing datetime setting is disabled.");
$this->assertText('There is data for this field in the database. The field settings can no longer be changed.');
}
}

View file

@ -0,0 +1,203 @@
<?php
namespace Drupal\datetime\Tests\Views;
use Drupal\views\Views;
/**
* Tests the Drupal\datetime\Plugin\views\filter\Date handler.
*
* @group datetime
*/
class ArgumentDateTimeTest extends DateTimeHandlerTestBase {
/**
* {@inheritdoc}
*/
public static $testViews = ['test_argument_datetime'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Add some basic test nodes.
$dates = [
'2000-10-10',
'2001-10-10',
'2002-01-01',
];
foreach ($dates as $date) {
$this->nodes[] = $this->drupalCreateNode([
'field_date' => [
'value' => $date,
]
]);
}
}
/**
* Test year argument.
*
* @see \Drupal\datetime\Plugin\views\argument\YearDate
*/
public function testDatetimeArgumentYear() {
$view = Views::getView('test_argument_datetime');
// The 'default' display has the 'year' argument.
$view->setDisplay('default');
$this->executeView($view, ['2000']);
$expected = [];
$expected[] = ['nid' => $this->nodes[0]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
$view->setDisplay('default');
$this->executeView($view, ['2002']);
$expected = [];
$expected[] = ['nid' => $this->nodes[2]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
}
/**
* Test month argument.
*
* @see \Drupal\datetime\Plugin\views\argument\MonthDate
*/
public function testDatetimeArgumentMonth() {
$view = Views::getView('test_argument_datetime');
// The 'embed_1' display has the 'month' argument.
$view->setDisplay('embed_1');
$this->executeView($view, ['10']);
$expected = [];
$expected[] = ['nid' => $this->nodes[0]->id()];
$expected[] = ['nid' => $this->nodes[1]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
$view->setDisplay('embed_1');
$this->executeView($view, ['01']);
$expected = [];
$expected[] = ['nid' => $this->nodes[2]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
}
/**
* Test day argument.
*
* @see \Drupal\datetime\Plugin\views\argument\DayDate
*/
public function testDatetimeArgumentDay() {
$view = Views::getView('test_argument_datetime');
// The 'embed_2' display has the 'day' argument.
$view->setDisplay('embed_2');
$this->executeView($view, ['10']);
$expected = [];
$expected[] = ['nid' => $this->nodes[0]->id()];
$expected[] = ['nid' => $this->nodes[1]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
$view->setDisplay('embed_2');
$this->executeView($view, ['01']);
$expected = [];
$expected[] = ['nid' => $this->nodes[2]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
}
/**
* Test year, month, and day arguments combined.
*/
public function testDatetimeArgumentAll() {
$view = Views::getView('test_argument_datetime');
// The 'embed_3' display has year, month, and day arguments.
$view->setDisplay('embed_3');
$this->executeView($view, ['2000', '10', '10']);
$expected = [];
$expected[] = ['nid' => $this->nodes[0]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
$view->setDisplay('embed_3');
$this->executeView($view, ['2002', '01', '01']);
$expected = [];
$expected[] = ['nid' => $this->nodes[2]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
}
/**
* Test week WW argument.
*/
public function testDatetimeArgumentWeek() {
$view = Views::getView('test_argument_datetime');
// The 'embed_4' display has WW argument.
$view->setDisplay('embed_4');
$this->executeView($view, ['41']);
$expected = [];
$expected[] = ['nid' => $this->nodes[0]->id()];
$expected[] = ['nid' => $this->nodes[1]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
$view->setDisplay('embed_4');
$this->executeView($view, ['01']);
$expected = [];
$expected[] = ['nid' => $this->nodes[2]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
}
/**
* Test full_date CCYYMMDD argument.
*/
public function testDatetimeArgumentFullDate() {
$view = Views::getView('test_argument_datetime');
// The 'embed_5' display has CCYYMMDD argument.
$view->setDisplay('embed_5');
$this->executeView($view, ['20001010']);
$expected = [];
$expected[] = ['nid' => $this->nodes[0]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
$view->setDisplay('embed_5');
$this->executeView($view, ['20020101']);
$expected = [];
$expected[] = ['nid' => $this->nodes[2]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
}
/**
* Test year_month CCYYMM argument.
*/
public function testDatetimeArgumentYearMonth() {
$view = Views::getView('test_argument_datetime');
// The 'embed_6' display has CCYYMM argument.
$view->setDisplay('embed_6');
$this->executeView($view, ['200010']);
$expected = [];
$expected[] = ['nid' => $this->nodes[0]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
$view->setDisplay('embed_6');
$this->executeView($view, ['200201']);
$expected = [];
$expected[] = ['nid' => $this->nodes[2]->id()];
$this->assertIdenticalResultset($view, $expected, $this->map);
$view->destroy();
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Drupal\datetime\Tests\Views;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\field\Entity\FieldConfig;
use Drupal\node\Entity\NodeType;
use Drupal\views\Tests\Handler\HandlerTestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Base class for testing datetime handlers.
*/
abstract class DateTimeHandlerTestBase extends HandlerTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['datetime_test', 'node', 'datetime'];
/**
* Name of the field.
*
* Note, this is used in the default test view.
*
* @var string
*/
protected static $field_name = 'field_date';
/**
* Nodes to test.
*
* @var \Drupal\node\NodeInterface[]
*/
protected $nodes = [];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Add a date field to page nodes.
$node_type = NodeType::create([
'type' => 'page',
'name' => 'page'
]);
$node_type->save();
$fieldStorage = FieldStorageConfig::create([
'field_name' => static::$field_name,
'entity_type' => 'node',
'type' => 'datetime',
'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATETIME],
]);
$fieldStorage->save();
$field = FieldConfig::create([
'field_storage' => $fieldStorage,
'bundle' => 'page',
'required' => TRUE,
]);
$field->save();
// Views needs to be aware of the new field.
$this->container->get('views.views_data')->clear();
// Set column map.
$this->map = [
'nid' => 'nid',
];
// Load test views.
ViewTestData::createTestViews(get_class($this), ['datetime_test']);
}
}

View file

@ -0,0 +1,110 @@
<?php
namespace Drupal\datetime\Tests\Views;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\views\Views;
/**
* Tests date-only fields.
*
* @group datetime
*/
class FilterDateTest extends DateTimeHandlerTestBase {
/**
* {@inheritdoc}
*/
public static $testViews = ['test_filter_datetime'];
/**
* For offset tests, set to the current time.
*/
protected static $date;
/**
* {@inheritdoc}
*
* Create nodes with relative dates of yesterday, today, and tomorrow.
*/
protected function setUp() {
parent::setUp();
// Set to 'today'.
static::$date = REQUEST_TIME;
// Change field storage to date-only.
$storage = FieldStorageConfig::load('node.' . static::$field_name);
$storage->setSetting('datetime_type', DateTimeItem::DATETIME_TYPE_DATE);
$storage->save();
$dates = [
// Tomorrow.
\Drupal::service('date.formatter')->format(static::$date + 86400, 'custom', DATETIME_DATE_STORAGE_FORMAT, DATETIME_STORAGE_TIMEZONE),
// Today.
\Drupal::service('date.formatter')->format(static::$date, 'custom', DATETIME_DATE_STORAGE_FORMAT, DATETIME_STORAGE_TIMEZONE),
// Yesterday.
\Drupal::service('date.formatter')->format(static::$date - 86400, 'custom', DATETIME_DATE_STORAGE_FORMAT, DATETIME_STORAGE_TIMEZONE),
];
foreach ($dates as $date) {
$this->nodes[] = $this->drupalCreateNode([
'field_date' => [
'value' => $date,
]
]);
}
}
/**
* Test offsets with date-only fields.
*/
public function testDateOffsets() {
$view = Views::getView('test_filter_datetime');
$field = static::$field_name . '_value';
// Test simple operations.
$view->initHandlers();
// A greater than or equal to 'now', should return the 'today' and
// the 'tomorrow' node.
$view->filter[$field]->operator = '>=';
$view->filter[$field]->value['type'] = 'offset';
$view->filter[$field]->value['value'] = 'now';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[0]->id()],
['nid' => $this->nodes[1]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
// Only dates in the past.
$view->initHandlers();
$view->filter[$field]->operator = '<';
$view->filter[$field]->value['type'] = 'offset';
$view->filter[$field]->value['value'] = 'now';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[2]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
// Test offset for between operator. Only the 'tomorrow' node should appear.
$view->initHandlers();
$view->filter[$field]->operator = 'between';
$view->filter[$field]->value['type'] = 'offset';
$view->filter[$field]->value['max'] = '+2 days';
$view->filter[$field]->value['min'] = '+1 day';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[0]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
}
}

View file

@ -0,0 +1,190 @@
<?php
namespace Drupal\datetime\Tests\Views;
use Drupal\views\Views;
/**
* Tests the Drupal\datetime\Plugin\views\filter\Date handler.
*
* @group datetime
*/
class FilterDateTimeTest extends DateTimeHandlerTestBase {
/**
* {@inheritdoc}
*/
public static $testViews = ['test_filter_datetime'];
/**
* For offset tests, set a date 1 day in the future.
*/
protected static $date;
/**
* Use a non-UTC timezone.
*/
protected static $timezone = 'America/Vancouver';
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
static::$date = REQUEST_TIME + 86400;
// Set the timezone.
date_default_timezone_set(static::$timezone);
// Add some basic test nodes.
$dates = [
'2000-10-10T00:01:30',
'2001-10-10T12:12:12',
'2002-10-10T14:14:14',
// The date storage timezone is used (this mimics the steps taken in the
// widget: \Drupal\datetime\Plugin\Field\FieldWidget::messageFormValues().
\Drupal::service('date.formatter')->format(static::$date, 'custom', DATETIME_DATETIME_STORAGE_FORMAT, DATETIME_STORAGE_TIMEZONE),
];
foreach ($dates as $date) {
$this->nodes[] = $this->drupalCreateNode([
'field_date' => [
'value' => $date,
]
]);
}
}
/**
* Test filter operations.
*/
public function testDatetimeFilter() {
$this->_testOffset();
$this->_testBetween();
$this->_testExact();
}
/**
* Test offset operations.
*/
protected function _testOffset() {
$view = Views::getView('test_filter_datetime');
$field = static::$field_name . '_value';
// Test simple operations.
$view->initHandlers();
$view->filter[$field]->operator = '>';
$view->filter[$field]->value['type'] = 'offset';
$view->filter[$field]->value['value'] = '+1 hour';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[3]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
// Test offset for between operator.
$view->initHandlers();
$view->filter[$field]->operator = 'between';
$view->filter[$field]->value['type'] = 'offset';
$view->filter[$field]->value['max'] = '+2 days';
$view->filter[$field]->value['min'] = '+1 hour';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[3]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
}
/**
* Test between operations.
*/
protected function _testBetween() {
$view = Views::getView('test_filter_datetime');
$field = static::$field_name . '_value';
// Test between with min and max.
$view->initHandlers();
$view->filter[$field]->operator = 'between';
$view->filter[$field]->value['min'] = '2001-01-01';
$view->filter[$field]->value['max'] = '2002-01-01';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[1]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
// Test between with just max.
$view->initHandlers();
$view->filter[$field]->operator = 'between';
$view->filter[$field]->value['max'] = '2002-01-01';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[0]->id()],
['nid' => $this->nodes[1]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
// Test not between with min and max.
$view->initHandlers();
$view->filter[$field]->operator = 'not between';
$view->filter[$field]->value['min'] = '2001-01-01';
// Set maximum date to date of node 1 to test range borders.
$view->filter[$field]->value['max'] = '2001-10-10T12:12:12';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[0]->id()],
['nid' => $this->nodes[2]->id()],
['nid' => $this->nodes[3]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
// Test not between with just max.
$view->initHandlers();
$view->filter[$field]->operator = 'not between';
$view->filter[$field]->value['max'] = '2001-01-01';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[1]->id()],
['nid' => $this->nodes[2]->id()],
['nid' => $this->nodes[3]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
}
/**
* Test exact date matching.
*/
protected function _testExact() {
$view = Views::getView('test_filter_datetime');
$field = static::$field_name . '_value';
// Test between with min and max.
$view->initHandlers();
$view->filter[$field]->operator = '=';
$view->filter[$field]->value['min'] = '';
$view->filter[$field]->value['max'] = '';
// Use the date from node 3. Use the site timezone (mimics a value entered
// through the UI).
$view->filter[$field]->value['value'] = \Drupal::service('date.formatter')->format(static::$date, 'custom', DATETIME_DATETIME_STORAGE_FORMAT, static::$timezone);
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[3]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
}
}

View file

@ -0,0 +1,125 @@
<?php
namespace Drupal\datetime\Tests\Views;
use Drupal\views\Views;
/**
* Tests for core Drupal\datetime\Plugin\views\sort\Date handler.
*
* @group datetime
*/
class SortDateTimeTest extends DateTimeHandlerTestBase {
/**
* {@inheritdoc}
*/
public static $testViews = ['test_sort_datetime'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Add some basic test nodes.
$dates = [
'2014-10-10T00:03:00',
'2000-10-10T00:01:00',
'2000-10-10T00:02:00',
'2000-10-10T00:03:00',
'2000-10-10T00:03:02',
'2000-10-10T00:03:01',
'2000-10-10T00:03:03',
];
foreach ($dates as $date) {
$this->nodes[] = $this->drupalCreateNode([
'field_date' => [
'value' => $date,
]
]);
}
}
/**
* Tests the datetime sort handler.
*/
public function testDateTimeSort() {
$field = static::$field_name . '_value';
$view = Views::getView('test_sort_datetime');
// Set granularity to 'minute', and the secondary node ID order should
// define the order of nodes with the same minute.
$view->initHandlers();
$view->sort[$field]->options['granularity'] = 'minute';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[0]->id()],
['nid' => $this->nodes[3]->id()],
['nid' => $this->nodes[4]->id()],
['nid' => $this->nodes[5]->id()],
['nid' => $this->nodes[6]->id()],
['nid' => $this->nodes[2]->id()],
['nid' => $this->nodes[1]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
// Check ASC.
$view->initHandlers();
$field = static::$field_name . '_value';
$view->sort[$field]->options['order'] = 'ASC';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[1]->id()],
['nid' => $this->nodes[2]->id()],
['nid' => $this->nodes[3]->id()],
['nid' => $this->nodes[5]->id()],
['nid' => $this->nodes[4]->id()],
['nid' => $this->nodes[6]->id()],
['nid' => $this->nodes[0]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
// Change granularity to 'year', and the secondary node ID order should
// define the order of nodes with the same year.
$view->initHandlers();
$view->sort[$field]->options['granularity'] = 'year';
$view->sort[$field]->options['order'] = 'DESC';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[0]->id()],
['nid' => $this->nodes[1]->id()],
['nid' => $this->nodes[2]->id()],
['nid' => $this->nodes[3]->id()],
['nid' => $this->nodes[4]->id()],
['nid' => $this->nodes[5]->id()],
['nid' => $this->nodes[6]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
// Change granularity to 'second'.
$view->initHandlers();
$view->sort[$field]->options['granularity'] = 'second';
$view->sort[$field]->options['order'] = 'DESC';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[0]->id()],
['nid' => $this->nodes[6]->id()],
['nid' => $this->nodes[4]->id()],
['nid' => $this->nodes[5]->id()],
['nid' => $this->nodes[3]->id()],
['nid' => $this->nodes[2]->id()],
['nid' => $this->nodes[1]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
}
}

View file

@ -0,0 +1,8 @@
name: 'Datetime test'
type: module
description: 'Provides default views for tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- views

View file

@ -0,0 +1,141 @@
langcode: und
status: true
dependencies: { }
id: test_argument_datetime
label: ''
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: '8'
display:
default:
display_options:
defaults:
fields: false
pager: false
sorts: false
arguments:
field_date_value_year:
field: field_date_value_year
id: field_date_value
table: node__field_date
plugin_id: datetime_year
fields:
id:
field: nid
id: nid
relationship: none
table: node_field_data
plugin_id: numeric
pager:
options:
offset: 0
type: none
sorts:
id:
field: nid
id: nid
order: ASC
relationship: none
table: node_field_data
plugin_id: numeric
display_plugin: default
display_title: Master
id: default
position: 0
embed_1:
display_options:
defaults:
arguments: false
arguments:
field_date_value_month:
field: field_date_value_month
id: field_date_value
table: node__field_date
plugin_id: datetime_month
display_plugin: embed
id: embed_1
display_title: ''
position: null
embed_2:
display_options:
defaults:
arguments: false
arguments:
field_date_value_day:
field: field_date_value_day
id: field_date_value
table: node__field_date
plugin_id: datetime_day
display_plugin: embed
id: embed_2
display_title: ''
position: null
embed_3:
display_options:
defaults:
arguments: false
arguments:
field_date_value_year:
field: field_date_value_year
id: field_date_value
table: node__field_date
plugin_id: datetime_year
field_date_value_month:
field: field_date_value_month
id: field_date_value
table: node__field_date
plugin_id: datetime_month
field_date_value_day:
field: field_date_value_day
id: field_date_value
table: node__field_date
plugin_id: datetime_day
display_plugin: embed
id: embed_2
display_title: ''
position: null
embed_4:
display_options:
defaults:
arguments: false
arguments:
field_date_value_day:
field: field_date_value_week
id: field_date_value
table: node__field_date
plugin_id: datetime_week
display_plugin: embed
id: embed_4
display_title: ''
position: null
embed_5:
display_options:
defaults:
arguments: false
arguments:
field_date_value_day:
field: field_date_value_full_date
id: field_date_value
table: node__field_date
plugin_id: datetime_full_date
display_plugin: embed
id: embed_5
display_title: ''
position: null
embed_6:
display_options:
defaults:
arguments: false
arguments:
field_date_value_day:
field: field_date_value_year_month
id: field_date_value
table: node__field_date
plugin_id: datetime_year_month
display_plugin: embed
id: embed_6
display_title: ''
position: null

View file

@ -0,0 +1,56 @@
langcode: und
status: true
dependencies:
module:
- node
id: test_filter_datetime
label: ''
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: '8'
display:
default:
display_options:
access:
type: none
cache:
type: none
exposed_form:
type: basic
fields:
nid:
field: nid
id: nid
table: node_field_data
plugin_id: node
filters:
field_date_value:
id: field_date_value
table: node__field_date
field: field_date_value
plugin_id: datetime
sorts:
id:
field: nid
id: nid
order: ASC
relationship: none
table: node_field_data
plugin_id: numeric
pager:
type: full
query:
options:
query_comment: ''
type: views_query
style:
type: default
row:
type: fields
display_plugin: default
display_title: Master
id: default
position: 0

View file

@ -0,0 +1,57 @@
langcode: und
status: true
dependencies:
module:
- node
id: test_sort_datetime
label: ''
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: '8'
display:
default:
display_options:
access:
type: none
cache:
type: none
exposed_form:
type: basic
fields:
nid:
field: nid
id: nid
table: node_field_data
plugin_id: node
sorts:
field_date_value:
field: field_date_value
id: field_date_value
relationship: none
table: node__field_date
order: DESC
plugin_id: datetime
id:
field: nid
id: nid
order: ASC
relationship: none
table: node_field_data
plugin_id: numeric
pager:
type: full
query:
options:
query_comment: ''
type: views_query
style:
type: default
row:
type: fields
display_plugin: default
display_title: Master
id: default
position: 0

View file

@ -0,0 +1,113 @@
<?php
namespace Drupal\Tests\datetime\Kernel;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests serializing a form with an injected datetime instance.
*
* @group datetime
*/
class DateTimeFormInjectionTest extends KernelTestBase implements FormInterface {
use DependencySerializationTrait;
/**
* A Dblog logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['system', 'datetime'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', ['key_value_expire', 'sequences']);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'datetime_test_injection_form';
}
/**
* Process callback.
*
* @param array $element
* Form element.
*
* @return array
* Processed element.
*/
public function process($element) {
return $element;
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['datelist_element'] = [
'#title' => 'datelist test',
'#type' => 'datelist',
'#default_value' => new DrupalDateTime('2000-01-01 00:00:00'),
'#date_part_order' => [
'month',
'day',
'year',
'hour',
'minute', 'ampm',
],
'#date_text_parts' => ['year'],
'#date_year_range' => '2010:2020',
'#date_increment' => 15,
];
$form['#process'][] = [$this, 'process'];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->assertTrue(TRUE);
$form_state->setRebuild();
}
/**
* Tests custom string injection serialization.
*/
public function testDatetimeSerialization() {
$form_state = new FormState();
$form_state->setRequestMethod('POST');
$form_state->setCached();
$form_builder = $this->container->get('form_builder');
$form_id = $form_builder->getFormId($this, $form_state);
$form = $form_builder->retrieveForm($form_id, $form_state);
$form_builder->prepareForm($form_id, $form, $form_state);
$form_builder->processForm($form_id, $form, $form_state);
}
}

View file

@ -0,0 +1,123 @@
<?php
namespace Drupal\Tests\datetime\Kernel;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests the new entity API for the date field type.
*
* @group datetime
*/
class DateTimeItemTest extends FieldKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('datetime');
protected function setUp() {
parent::setUp();
// Create a field with settings to validate.
$field_storage = FieldStorageConfig::create(array(
'field_name' => 'field_datetime',
'type' => 'datetime',
'entity_type' => 'entity_test',
'settings' => array('datetime_type' => 'date'),
));
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'entity_test',
'settings' => array(
'default_value' => 'blank',
),
]);
$field->save();
}
/**
* Tests using entity fields of the date field type.
*/
public function testDateTimeItem() {
// Verify entity creation.
$entity = EntityTest::create();
$value = '2014-01-01T20:00:00Z';
$entity->field_datetime = $value;
$entity->name->value = $this->randomMachineName();
$entity->save();
// Verify entity has been created properly.
$id = $entity->id();
$entity = EntityTest::load($id);
$this->assertTrue($entity->field_datetime instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->field_datetime[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->field_datetime->value, $value);
$this->assertEqual($entity->field_datetime[0]->value, $value);
// Verify changing the date value.
$new_value = $this->randomMachineName();
$entity->field_datetime->value = $new_value;
$this->assertEqual($entity->field_datetime->value, $new_value);
// Read changed entity and assert changed values.
$entity->save();
$entity = EntityTest::load($id);
$this->assertEqual($entity->field_datetime->value, $new_value);
// Test the generateSampleValue() method.
$entity = EntityTest::create();
$entity->field_datetime->generateSampleItems();
$this->entityValidateAndSave($entity);
}
/**
* Tests DateTimeItem::setValue().
*/
public function testSetValue() {
// Test DateTimeItem::setValue() using string.
$entity = EntityTest::create();
$value = '2014-01-01T20:00:00Z';
$entity->get('field_datetime')->set(0, $value);
$entity->save();
// Load the entity and ensure the field was saved correctly.
$id = $entity->id();
$entity = EntityTest::load($id);
$this->assertEqual($entity->field_datetime[0]->value, $value, 'DateTimeItem::setValue() works with string value.');
// Test DateTimeItem::setValue() using property array.
$entity = EntityTest::create();
$value = '2014-01-01T20:00:00Z';
$entity->set('field_datetime', $value);
$entity->save();
// Load the entity and ensure the field was saved correctly.
$id = $entity->id();
$entity = EntityTest::load($id);
$this->assertEqual($entity->field_datetime[0]->value, $value, 'DateTimeItem::setValue() works with array value.');
}
/**
* Tests setting the value of the DateTimeItem directly.
*/
public function testSetValueProperty() {
// Test Date::setValue().
$entity = EntityTest::create();
$value = '2014-01-01T20:00:00Z';
$entity->set('field_datetime', $value);
$entity->save();
// Load the entity and ensure the field was saved correctly.
$id = $entity->id();
$entity = EntityTest::load($id);
$this->assertEqual($entity->field_datetime[0]->value, $value, '"Value" property can be set directly.');
}
}