first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldFormatter;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
/**
* Plugin implementation of the 'Custom' formatter for 'datetime' fields.
*/
#[FieldFormatter(
id: 'datetime_custom',
label: new TranslatableMarkup('Custom'),
field_types: [
'datetime',
],
)]
class DateTimeCustomFormatter extends DateTimeFormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'date_format' => DateTimeItemInterface::DATETIME_STORAGE_FORMAT,
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
// @todo Evaluate removing this method in
// https://www.drupal.org/node/2793143 to determine if the behavior and
// markup in the base class implementation can be used instead.
$elements = [];
foreach ($items as $delta => $item) {
if (!empty($item->date)) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $date */
$date = $item->date;
$elements[$delta] = $this->buildDate($date);
}
}
return $elements;
}
/**
* {@inheritdoc}
*/
protected function formatDate($date) {
$format = $this->getSetting('date_format');
$timezone = $this->getSetting('timezone_override') ?: $date->getTimezone()->getName();
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'] = [
'#type' => 'textfield',
'#title' => $this->t('Date/time format'),
'#description' => $this->t('See <a href="https://www.php.net/manual/datetime.format.php#refsect1-datetime.format-parameters" 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,77 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldFormatter;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'Default' formatter for 'datetime' fields.
*/
#[FieldFormatter(
id: 'datetime_default',
label: new TranslatableMarkup('Default'),
field_types: [
'datetime',
],
)]
class DateTimeDefaultFormatter extends DateTimeFormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'format_type' => 'medium',
] + parent::defaultSettings();
}
/**
* {@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->getTimestamp(), $type);
$options[$type] = $type_info->label() . ' (' . $format . ')';
}
$form['format_type'] = [
'#type' => 'select',
'#title' => $this->t('Date format'),
'#description' => $this->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[] = $this->t('Format: @display', ['@display' => $this->formatDate($date)]);
return $summary;
}
}

View File

@@ -0,0 +1,248 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldFormatter;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Datetime\TimeZoneFormHelper;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for 'DateTime Field formatter' plugin implementations.
*/
abstract class DateTimeFormatterBase extends FormatterBase {
/**
* 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_type.manager')->getStorage('date_format')
);
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'timezone_override' => '',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form['timezone_override'] = [
'#type' => 'select',
'#title' => $this->t('Time zone override'),
'#description' => $this->t('The time zone selected here will always be used'),
'#options' => TimeZoneFormHelper::getOptionsListByRegion(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', ['@timezone' => $override]);
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
if ($item->date) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $date */
$date = $item->date;
$elements[$delta] = $this->buildDateWithIsoAttribute($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;
}
/**
* 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.
*
* @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 = DateTimeItemInterface::STORAGE_TIMEZONE;
}
else {
$timezone = date_default_timezone_get();
}
$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;
}
/**
* Creates a render array from a date object.
*
* @param \Drupal\Core\Datetime\DrupalDateTime $date
* A date object.
*
* @return array
* A render array.
*/
protected function buildDate(DrupalDateTime $date) {
$this->setTimeZone($date);
$build = [
'#markup' => $this->formatDate($date),
'#cache' => [
'contexts' => [
'timezone',
],
],
];
return $build;
}
/**
* Creates a render array from a date object with ISO date attribute.
*
* @param \Drupal\Core\Datetime\DrupalDateTime $date
* A date object.
*
* @return array
* A render array.
*/
protected function buildDateWithIsoAttribute(DrupalDateTime $date) {
// Create the ISO date in Universal Time.
$iso_date = $date->format("Y-m-d\TH:i:s") . 'Z';
$this->setTimeZone($date);
$build = [
'#theme' => 'time',
'#text' => $this->formatDate($date),
'#attributes' => [
'datetime' => $iso_date,
],
'#cache' => [
'contexts' => [
'timezone',
],
],
];
return $build;
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
/**
* Plugin implementation of the 'Plain' formatter for 'datetime' fields.
*/
#[FieldFormatter(
id: 'datetime_plain',
label: new TranslatableMarkup('Plain'),
field_types: [
'datetime',
],
)]
class DateTimePlainFormatter extends DateTimeFormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
if (!empty($item->date)) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $date */
$date = $item->date;
$elements[$delta] = $this->buildDate($date);
}
}
return $elements;
}
/**
* {@inheritdoc}
*/
protected function formatDate($date) {
$format = $this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
$timezone = $this->getSetting('timezone_override') ?: $date->getTimezone()->getName();
return $this->dateFormatter->format($date->getTimestamp(), 'custom', $format, $timezone != '' ? $timezone : NULL);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldFormatter;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldFormatter\TimestampAgoFormatter;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'Time ago' formatter for 'datetime' fields.
*/
#[FieldFormatter(
id: 'datetime_time_ago',
label: new TranslatableMarkup('Time ago'),
field_types: [
'datetime',
],
)]
class DateTimeTimeAgoFormatter extends TimestampAgoFormatter {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
$date = $item->date;
$output = [];
if (!empty($item->date)) {
$output = $this->formatDate($date);
}
$elements[$delta] = $output;
}
return $elements;
}
/**
* 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) {
return parent::formatTimestamp($date->getTimestamp());
}
}

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 = [
'#parents' => ['default_value_input'],
'default_date_type' => [
'#type' => 'select',
'#title' => $this->t('Default date'),
'#description' => $this->t('Set a default value for this date.'),
'#default_value' => $default_value[0]['default_date_type'] ?? '',
'#options' => [
static::DEFAULT_VALUE_NOW => $this->t('Current date'),
static::DEFAULT_VALUE_CUSTOM => $this->t('Relative date'),
],
'#empty_value' => '',
],
'default_date' => [
'#type' => 'textfield',
'#title' => $this->t('Relative default value'),
'#description' => $this->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' => [
'visible' => [
':input[id="edit-default-value-input-default-date-type"]' => ['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(['default_value_input', 'default_date']));
if (!$is_strtotime) {
$form_state->setErrorByName('default_value_input][default_date', $this->t('The relative date value entered is invalid.'));
}
}
}
/**
* {@inheritdoc}
*/
public function defaultValuesFormSubmit(array $element, array &$form, FormStateInterface $form_state) {
if ($form_state->getValue(['default_value_input', 'default_date_type'])) {
if ($form_state->getValue(['default_value_input', 'default_date_type']) == static::DEFAULT_VALUE_NOW) {
$form_state->setValueForElement($element['default_date'], static::DEFAULT_VALUE_NOW);
}
return [$form_state->getValue('default_value_input')];
}
return [];
}
/**
* {@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'], date_default_timezone_get());
$format = DateTimeItemInterface::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'], DateTimeItemInterface::STORAGE_TIMEZONE);
$format = DateTimeItemInterface::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 = [
[
'value' => $value,
'date' => $date,
],
];
}
return $default_value;
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
/**
* Plugin implementation of the 'datetime' field type.
*/
#[FieldType(
id: "datetime",
label: new TranslatableMarkup("Date"),
description: [
new TranslatableMarkup("Ideal when date and time needs to be input by users, like event dates and times"),
new TranslatableMarkup("Date or date and time stored in a readable string format"),
new TranslatableMarkup("Easy to read and understand for humans"),
],
category: "date_time",
default_widget: "datetime_default",
default_formatter: "datetime_default",
list_class: DateTimeFieldItemList::class,
constraints: ["DateTimeFormat" => []]
)]
class DateTimeItem extends FieldItemBase implements DateTimeItemInterface {
/**
* {@inheritdoc}
*/
public static function defaultStorageSettings() {
return [
'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(new TranslatableMarkup('Date value'))
->setRequired(TRUE);
$properties['date'] = DataDefinition::create('any')
->setLabel(new TranslatableMarkup('Computed date'))
->setDescription(new TranslatableMarkup('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 [
'columns' => [
'value' => [
'description' => 'The date value.',
'type' => 'varchar',
'length' => 20,
],
],
'indexes' => [
'value' => ['value'],
],
];
}
/**
* {@inheritdoc}
*/
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$element = [];
$element['datetime_type'] = [
'#type' => 'select',
'#title' => $this->t('Date type'),
'#description' => $this->t('Choose the type of date to create.'),
'#default_value' => $this->getSetting('datetime_type'),
'#options' => [
static::DATETIME_TYPE_DATETIME => $this->t('Date and time'),
static::DATETIME_TYPE_DATE => $this->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 = \Drupal::time()->getRequestTime() - mt_rand(0, 86400 * 365);
if ($type == DateTimeItem::DATETIME_TYPE_DATE) {
$values['value'] = gmdate(static::DATE_STORAGE_FORMAT, $timestamp);
}
else {
$values['value'] = gmdate(static::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,25 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldType;
/**
* Interface definition for Datetime items.
*/
interface DateTimeItemInterface {
/**
* Defines the timezone that dates should be stored in.
*/
const STORAGE_TIMEZONE = 'UTC';
/**
* Defines the format that date and time should be stored in.
*/
const DATETIME_STORAGE_FORMAT = 'Y-m-d\TH:i:s';
/**
* Defines the format that dates should be stored in.
*/
const DATE_STORAGE_FORMAT = 'Y-m-d';
}

View File

@@ -0,0 +1,140 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldWidget;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'datetime_datelist' widget.
*/
#[FieldWidget(
id: 'datetime_datelist',
label: new TranslatableMarkup('Select list'),
field_types: ['datetime'],
)]
class DateTimeDatelistWidget extends DateTimeWidgetBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'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);
// Wrap all of the select elements with a fieldset.
$element['#theme_wrappers'][] = 'fieldset';
$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.
$date_part_order = match ($date_order) {
'YMD' => ['year', 'month', 'day'],
'MDY' => ['month', 'day', 'year'],
'DMY' => ['day', 'month', 'year'],
};
$date_part_order = match ($time_type) {
'24' => array_merge($date_part_order, ['hour', 'minute']),
'12' => array_merge($date_part_order, ['hour', 'minute', 'ampm']),
default => $date_part_order,
};
$element['value'] = [
'#type' => 'datelist',
'#date_increment' => $increment,
'#date_part_order' => $date_part_order,
] + $element['value'];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element = parent::settingsForm($form, $form_state);
$element['date_order'] = [
'#type' => 'select',
'#title' => $this->t('Date part order'),
'#default_value' => $this->getSetting('date_order'),
'#options' => [
'MDY' => $this->t('Month/Day/Year'),
'DMY' => $this->t('Day/Month/Year'),
'YMD' => $this->t('Year/Month/Day'),
],
];
if ($this->getFieldSetting('datetime_type') == 'datetime') {
$element['time_type'] = [
'#type' => 'select',
'#title' => $this->t('Time type'),
'#default_value' => $this->getSetting('time_type'),
'#options' => ['24' => $this->t('24 hour time'), '12' => $this->t('12 hour time')],
];
$element['increment'] = [
'#type' => 'select',
'#title' => $this->t('Time increments'),
'#default_value' => $this->getSetting('increment'),
'#options' => [
1 => $this->t('1 minute'),
5 => $this->t('@count minutes', ['@count' => 5]),
10 => $this->t('@count minutes', ['@count' => 10]),
15 => $this->t('@count minutes', ['@count' => 15]),
30 => $this->t('@count minutes', ['@count' => 30]),
],
];
}
else {
$element['time_type'] = [
'#type' => 'hidden',
'#value' => 'none',
];
$element['increment'] = [
'#type' => 'hidden',
'#value' => $this->getSetting('increment'),
];
}
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = $this->t('Date part order: @order', ['@order' => $this->getSetting('date_order')]);
if ($this->getFieldSetting('datetime_type') == 'datetime') {
$summary[] = $this->t('Time type: @time_type', ['@time_type' => $this->getSetting('time_type')]);
$summary[] = $this->t('Time increments: @increment', ['@increment' => $this->getSetting('increment')]);
}
return $summary;
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Drupal\datetime\Plugin\Field\FieldWidget;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'datetime_default' widget.
*/
#[FieldWidget(
id: 'datetime_default',
label: new TranslatableMarkup('Date and time'),
field_types: ['datetime'],
)]
class DateTimeDefaultWidget extends DateTimeWidgetBase {
/**
* 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_type.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);
// If the field is date-only, make sure the title is displayed. Otherwise,
// wrap everything in a fieldset, and the title will be shown in the legend.
if ($this->getFieldSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
$element['value']['#title'] = $this->fieldDefinition->getLabel();
$element['value']['#description'] = $this->fieldDefinition->getDescription();
}
else {
$element['#theme_wrappers'][] = 'fieldset';
}
// 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'] += [
'#date_date_format' => $date_format,
'#date_date_element' => $date_type,
'#date_date_callbacks' => [],
'#date_time_format' => $time_format,
'#date_time_element' => $time_type,
'#date_time_callbacks' => [],
];
return $element;
}
}

View File

@@ -0,0 +1,102 @@
<?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;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
/**
* Base class for the 'datetime_*' widgets.
*/
class DateTimeWidgetBase extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element['value'] = [
'#type' => 'datetime',
'#default_value' => NULL,
'#date_increment' => 1,
'#date_timezone' => date_default_timezone_get(),
'#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'] = DateTimeItemInterface::STORAGE_TIMEZONE;
}
if ($items[$delta]->date) {
$element['value']['#default_value'] = $this->createDefaultValue($items[$delta]->date, $element['value']['#date_timezone']);
}
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.
$datetime_type = $this->getFieldSetting('datetime_type');
if ($datetime_type === DateTimeItem::DATETIME_TYPE_DATE) {
$storage_format = DateTimeItemInterface::DATE_STORAGE_FORMAT;
}
else {
$storage_format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
}
$storage_timezone = new \DateTimezone(DateTimeItemInterface::STORAGE_TIMEZONE);
foreach ($values as &$item) {
if (!empty($item['value']) && $item['value'] instanceof DrupalDateTime) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $date */
$date = $item['value'];
// Adjust the date for storage.
$item['value'] = $date->setTimezone($storage_timezone)->format($storage_format);
}
}
return $values;
}
/**
* Creates a date object for use as a default value.
*
* This will take a default value, apply the proper timezone for display in
* a widget, and set the default time for date-only fields.
*
* @param \Drupal\Core\Datetime\DrupalDateTime $date
* The UTC default date.
* @param string $timezone
* The timezone to apply.
*
* @return \Drupal\Core\Datetime\DrupalDateTime
* A date object for use as a default value in a field widget.
*/
protected function createDefaultValue($date, $timezone) {
// The date was created and verified during field_load(), so it is safe to
// use without further inspection.
$year = $date->format('Y');
$month = $date->format('m');
$day = $date->format('d');
$date->setTimezone(new \DateTimeZone($timezone));
if ($this->getFieldSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
$date->setDefaultDateTime();
// Reset the date to handle cases where the UTC offset is greater than
// 12 hours.
$date->setDate($year, $month, $day);
}
return $date;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Drupal\datetime\Plugin\Validation\Constraint;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Validation\Attribute\Constraint;
use Symfony\Component\Validator\Constraint as SymfonyConstraint;
/**
* Validation constraint for DateTime items to ensure the format is correct.
*/
#[Constraint(
id: 'DateTimeFormat',
label: new TranslatableMarkup('Datetime format valid for datetime type.', [], ['context' => 'Validation'])
)]
class DateTimeFormatConstraint extends SymfonyConstraint {
/**
* Message for when the value isn't a string.
*
* @var string
*/
public $badType = "The datetime value must be a string.";
/**
* Message for when the value isn't in the proper format.
*
* @var string
*/
public $badFormat = "The datetime value '@value' is invalid for the format '@format'";
/**
* Message for when the value did not parse properly.
*
* @var string
*/
public $badValue = "The datetime value '@value' did not parse properly for the format '@format'";
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Drupal\datetime\Plugin\Validation\Constraint;
use Drupal\Component\Datetime\DateTimePlus;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Constraint validator for DateTime items to ensure the format is correct.
*/
class DateTimeFormatConstraintValidator extends ConstraintValidator {
/**
* {@inheritdoc}
*/
public function validate($item, Constraint $constraint) {
/** @var \Drupal\datetime\Plugin\Field\FieldType\DateTimeItem $item */
if (isset($item)) {
$value = $item->getValue()['value'];
if (!is_string($value)) {
$this->context->addViolation($constraint->badType);
}
else {
$datetime_type = $item->getFieldDefinition()->getSetting('datetime_type');
$format = $datetime_type === DateTimeItem::DATETIME_TYPE_DATE ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
$date = NULL;
try {
$date = DateTimePlus::createFromFormat($format, $value, new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE));
}
catch (\InvalidArgumentException $e) {
$this->context->addViolation($constraint->badFormat, [
'@value' => $value,
'@format' => $format,
]);
return;
}
catch (\UnexpectedValueException $e) {
$this->context->addViolation($constraint->badValue, [
'@value' => $value,
'@format' => $format,
]);
return;
}
if ($date === NULL || $date->hasErrors()) {
$this->context->addViolation($constraint->badFormat, [
'@value' => $value,
'@format' => $format,
]);
}
}
}
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace Drupal\datetime\Plugin\migrate\field;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Attribute\MigrateField;
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
// cspell:ignore todate
/**
* Provides a field plugin for date and time fields.
*/
#[MigrateField(
id: 'datetime',
core: [6, 7],
type_map: [
'date' => 'datetime',
'datestamp' => 'timestamp',
'datetime' => 'datetime',
],
source_module: 'date',
destination_module: 'datetime',
)]
class DateField extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return [
'date_default' => 'datetime_default',
'format_interval' => 'datetime_time_ago',
// The date_plain formatter exists in Drupal 7 but not Drupal 6. It is
// added here because this plugin is declared for Drupal 6 and Drupal 7.
'date_plain' => 'datetime_plain',
];
}
/**
* {@inheritdoc}
*/
public function getFieldWidgetMap() {
return [
'date' => 'datetime_default',
'datetime' => 'datetime_default',
'datestamp' => 'datetime_timestamp',
];
}
/**
* {@inheritdoc}
*/
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
$to_format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
if (isset($data['field_definition']['data'])) {
$field_data = unserialize($data['field_definition']['data']);
if (isset($field_data['settings']['granularity'])) {
$granularity = $field_data['settings']['granularity'];
$collected_date_attributes = is_numeric(array_keys($granularity)[0])
? $granularity
: array_keys(array_filter($granularity));
if (empty(array_intersect($collected_date_attributes, ['hour', 'minute', 'second']))) {
$to_format = DateTimeItemInterface::DATE_STORAGE_FORMAT;
}
}
}
switch ($data['type']) {
case 'date':
$from_format = 'Y-m-d\TH:i:s';
break;
case 'datestamp':
$from_format = 'U';
$to_format = 'U';
break;
case 'datetime':
$from_format = 'Y-m-d H:i:s';
break;
default:
throw new MigrateException(sprintf('Field %s of type %s is an unknown date field type.', $field_name, var_export($data['type'], TRUE)));
}
$process = [
'value' => [
'plugin' => 'format_date',
'from_format' => $from_format,
'to_format' => $to_format,
'source' => 'value',
],
];
// If the 'todate' setting is specified the field is now a 'daterange' and
// so set the end value. If the datetime_range module is not enabled on the
// destination then end_value is ignored and a message is logged in the
// relevant migrate message table.
if (!empty($field_data['settings']['todate'])) {
$process['end_value'] = $process['value'];
$process['end_value']['source'] = 'value2';
}
$process = [
'plugin' => 'sub_process',
'source' => $field_name,
'process' => $process,
];
$migration->mergeProcessOfProperty($field_name, $process);
}
/**
* {@inheritdoc}
*/
public function getFieldType(Row $row) {
$field_type = parent::getFieldType($row);
// If the 'todate' setting is specified then change the field type to
// 'daterange' so we can migrate the end date.
if ($field_type === 'datetime' && !empty($row->get('settings/todate'))) {
if (\Drupal::service('module_handler')->moduleExists('datetime_range')) {
return 'daterange';
}
else {
throw new MigrateException(sprintf("Can't migrate field '%s' with 'todate' settings. Enable the datetime_range module. See https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#datetime", $row->get('field_name')));
}
}
return $field_type;
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Drupal\datetime\Plugin\views\argument;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\views\Attribute\ViewsArgument;
use Drupal\views\FieldAPIHandlerTrait;
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\ManyToOneHelper
*
* @ingroup views_argument_handlers
*/
#[ViewsArgument(
id: 'datetime',
)]
class Date extends NumericDate {
use FieldAPIHandlerTrait;
/**
* Determines if the timezone offset is calculated.
*
* @var bool
*/
protected $calculateOffset = TRUE;
/**
* {@inheritdoc}
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
RouteMatchInterface $route_match,
DateFormatterInterface $date_formatter,
?TimeInterface $time = NULL,
) {
if (!$time) {
@trigger_error('Calling ' . __METHOD__ . ' without the $time argument is deprecated in drupal:10.3.0 and it will be required in drupal:11.0.0. See https://www.drupal.org/node/3395991', E_USER_DEPRECATED);
$time = \Drupal::service('datetime.time');
}
parent::__construct($configuration, $plugin_id, $plugin_definition, $route_match, $date_formatter, $time);
$definition = $this->getFieldStorageDefinition();
if ($definition->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
// Timezone offset calculation is not applicable to dates that are stored
// as date-only.
$this->calculateOffset = FALSE;
}
}
/**
* {@inheritdoc}
*/
public function getDateField() {
// Use string date storage/formatting since datetime fields are stored as
// strings rather than UNIX timestamps.
return $this->query->getDateField("$this->tableAlias.$this->realField", TRUE, $this->calculateOffset);
}
/**
* {@inheritdoc}
*/
public function getDateFormat($format) {
// Pass in the string-field option.
return $this->query->getDateFormat($this->getDateField(), $format, TRUE);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,182 @@
<?php
namespace Drupal\datetime\Plugin\views\filter;
use Drupal\Component\Datetime\DateTimePlus;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\views\Attribute\ViewsFilter;
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 = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
/**
* Determines if the timezone offset is calculated.
*
* @var bool
*/
protected $calculateOffset = TRUE;
/**
* The request stack used to determine 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;
$definition = $this->getFieldStorageDefinition();
if ($definition->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
// Date format depends on field storage format.
$this->dateFormat = DateTimeItemInterface::DATE_STORAGE_FORMAT;
// Timezone offset calculation is not applicable to dates that are stored
// as date-only.
$this->calculateOffset = FALSE;
}
}
/**
* {@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) {
$timezone = $this->getTimezone();
$origin_offset = $this->getOffset($this->value['min'], $timezone);
// Although both 'min' and 'max' values are required, default empty 'min'
// value as UNIX timestamp 0.
$min = (!empty($this->value['min'])) ? $this->value['min'] : '@0';
// Convert to ISO format and format for query. UTC timezone is used since
// dates are stored in UTC.
$a = new DateTimePlus($min, new \DateTimeZone($timezone));
$a = $this->query->getDateFormat($this->query->getDateField("'" . $this->dateFormatter->format($a->getTimestamp() + $origin_offset, 'custom', DateTimeItemInterface::DATETIME_STORAGE_FORMAT, DateTimeItemInterface::STORAGE_TIMEZONE) . "'", TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
$b = new DateTimePlus($this->value['max'], new \DateTimeZone($timezone));
$b = $this->query->getDateFormat($this->query->getDateField("'" . $this->dateFormatter->format($b->getTimestamp() + $origin_offset, 'custom', DateTimeItemInterface::DATETIME_STORAGE_FORMAT, DateTimeItemInterface::STORAGE_TIMEZONE) . "'", TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
// This is safe because we are manually scrubbing the values.
$operator = strtoupper($this->operator);
$field = $this->query->getDateFormat($this->query->getDateField($field, TRUE, $this->calculateOffset), $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) {
$timezone = $this->getTimezone();
$origin_offset = $this->getOffset($this->value['value'], $timezone);
// Convert to ISO. UTC timezone is used since dates are stored in UTC.
$value = new DateTimePlus($this->value['value'], new \DateTimeZone($timezone));
$value = $this->query->getDateFormat($this->query->getDateField("'" . $this->dateFormatter->format($value->getTimestamp() + $origin_offset, 'custom', DateTimeItemInterface::DATETIME_STORAGE_FORMAT, DateTimeItemInterface::STORAGE_TIMEZONE) . "'", TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
// This is safe because we are manually scrubbing the value.
$field = $this->query->getDateFormat($this->query->getDateField($field, TRUE, $this->calculateOffset), $this->dateFormat, TRUE);
$this->query->addWhereExpression($this->options['group'], "$field $this->operator $value");
}
/**
* Get the proper time zone to use in computations.
*
* Date-only fields do not have a time zone associated with them, so the
* filter input needs to use UTC for reference. Otherwise, use the time zone
* for the current user.
*
* @return string
* The time zone name.
*/
protected function getTimezone() {
return $this->dateFormat === DateTimeItemInterface::DATE_STORAGE_FORMAT
? DateTimeItemInterface::STORAGE_TIMEZONE
: date_default_timezone_get();
}
/**
* Get the proper offset from UTC to use in computations.
*
* @param string $time
* A date/time string compatible with \DateTime. It is used as the
* reference for computing the offset, which can vary based on the time
* zone rules.
* @param string $timezone
* The time zone that $time is in.
*
* @return int
* The computed offset in seconds.
*/
protected function getOffset($time, $timezone) {
// Date-only fields do not have a time zone or offset from UTC associated
// with them. For relative (i.e. 'offset') comparisons, we need to compute
// the user's offset from UTC for use in the query.
$origin_offset = 0;
if ($this->dateFormat === DateTimeItemInterface::DATE_STORAGE_FORMAT && $this->value['type'] === 'offset') {
$origin_offset = $origin_offset + timezone_offset_get(new \DateTimeZone(date_default_timezone_get()), new \DateTime($time, new \DateTimeZone($timezone)));
}
return $origin_offset;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Drupal\datetime\Plugin\views\sort;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\views\Attribute\ViewsSort;
use Drupal\views\FieldAPIHandlerTrait;
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 {
use FieldAPIHandlerTrait;
/**
* Determines if the timezone offset is calculated.
*
* @var bool
*/
protected $calculateOffset = TRUE;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$definition = $this->getFieldStorageDefinition();
if ($definition->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
// Timezone offset calculation is not applicable to dates that are stored
// as date-only.
$this->calculateOffset = FALSE;
}
}
/**
* {@inheritdoc}
*
* Override to account for dates stored as strings.
*/
public function getDateField() {
// Use string date storage/formatting since datetime fields are stored as
// strings rather than UNIX timestamps.
return $this->query->getDateField("$this->tableAlias.$this->realField", TRUE, $this->calculateOffset);
}
/**
* {@inheritdoc}
*
* Overridden in order to pass in the string date flag.
*/
public function getDateFormat($format) {
return $this->query->getDateFormat($this->getDateField(), $format, TRUE);
}
}