(Generated with the help of an LLM.)

Problem/Motivation

Optional End Date replaces the core date range formatters daterange_default, daterange_custom and daterange_plain (see OptionalEndDateHooks::fieldFormatterInfoAlter()). All three replacements share OptionalEndDateDateTimeRangeTrait::viewElements().

Since #3458951, this method always calls renderStartEndWithIsoAttribute() and buildDateWithIsoAttribute(). In core, only the Default formatter uses these ISO helpers. Core's DateRangeCustomFormatter and DateRangePlainFormatter use renderStartEnd() and buildDate(). The doc comment of DateRangeCustomFormatter says it "renders the data range as plain text".

As a result, once this module is enabled, the Custom and Plain formatters output a <time datetime="…">…</time> element instead of plain text. This is a regression compared to core and breaks every place that uses the formatted value as text:

  • REST/Views serializer exports (for example rest_views field_export) now contain HTML:

    "19.10.1946" becomes "<time datetime=\"1946-10-19T12:00:00Z\">19.10.1946</time>\n" (including the trailing newline from time.html.twig).
  • Views rewrite tokens, for example <section id="year-{{ field_date }}">, now produce invalid attributes.
  • Views grouping titles and CSV exports get extra markup and whitespace.

Steps to reproduce

  1. Install Drupal 10.3+/11 with datetime_range, views, rest, serialization and optional_end_date 2.0.x.
  2. Add a daterange field (date only) to a content type and create a node with start date 1946-10-19 and the same end date.
  3. Create a view with a REST export display and add the field using the Custom formatter, date format d.m.Y.
  4. Open the export.

Expected (core behaviour, module disabled): "19.10.1946"

Actual: "<time datetime=\"1946-10-19T12:00:00Z\">19.10.1946</time>\n"

The same happens with the Plain formatter and with ranges (19.10.1946 - 20.10.1946).

Proposed resolution

Let each formatter decide whether to use the ISO helpers, as core does:

  • Add protected function useIsoAttribute(): bool to OptionalEndDateDateTimeRangeTrait, returning TRUE by default (Default formatter, unchanged).
  • In viewElements(), call renderStartEnd()/buildDate() when it returns FALSE, and renderStartEndWithIsoAttribute()/buildDateWithIsoAttribute() otherwise.
  • Merge $item->_attributes only for the ISO variant, as core does, and only if an element was built.
  • Override useIsoAttribute() to return FALSE in OptionalEndDateDateRangeCustomFormatter and OptionalEndDateDateRangePlainFormatter.

Open-ended ranges keep working: when there is no end date, the start date alone is rendered, as plain text for Custom/Plain and as <time> for Default.

protected function useIsoAttribute(): bool {
  return TRUE;
}

// In viewElements():
$elements[$delta] = $use_iso_attribute
  ? $this->renderStartEndWithIsoAttribute($start_date, $separator, $end_date)
  : $this->renderStartEnd($start_date, $separator, $end_date);

A patch is attached.

Remaining tasks

  • Review the patch.
  • Add a kernel test asserting that Custom/Plain output contains no <time> element, for both full and open-ended ranges.

User interface changes

None. The Custom and Plain formatters output plain text again, as in core.

API changes

New protected method OptionalEndDateDateTimeRangeTrait::useIsoAttribute().

Data model changes

None.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

internetter created an issue. See original summary.

internetter’s picture

Issue summary: View changes

internetter’s picture

Status: Active » Needs review

I added a working patch as MergeRequest.