--- /dev/null
+<input
+ type="{if $field->supportsTime()}datetime{else}date{/if}"
+ id="{$field->getPrefixedId()}_from"
+ name="{$field->getPrefixedId()}[from]"
+ value="{$field->getFromValue()}"
+ data-placeholder="{lang}wcf.date.period.start{/lang}"
+ {if !$field->getFieldClasses()|empty} class="{implode from=$field->getFieldClasses() item='class' glue=' '}{$class}{/implode}"{/if}
+ {if $field->isAutofocused()} autofocus{/if}
+ {if $field->isRequired()} required{/if}
+ {if $field->isImmutable()} disabled{/if}
+ {foreach from=$field->getFieldAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}
+>
+<input
+ type="{if $field->supportsTime()}datetime{else}date{/if}"
+ id="{$field->getPrefixedId()}_to"
+ name="{$field->getPrefixedId()}[to]"
+ value="{$field->getToValue()}"
+ data-placeholder="{lang}wcf.date.period.end{/lang}"
+ {if !$field->getFieldClasses()|empty} class="{implode from=$field->getFieldClasses() item='class' glue=' '}{$class}{/implode}"{/if}
+ {if $field->isRequired()} required{/if}
+ {if $field->isImmutable()} disabled{/if}
+ {foreach from=$field->getFieldAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}
+>
--- /dev/null
+/**
+ * Data handler for a date range form builder field in an Ajax form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.2
+ */
+import Field from "./Field";
+import { FormBuilderData } from "../Data";
+import DatePicker from "../../../Date/Picker";
+
+class DateRange extends Field {
+ #fromField: HTMLElement | null;
+ #toField: HTMLElement | null;
+
+ constructor(fieldId: string) {
+ super(fieldId);
+
+ this.#fromField = document.getElementById(this._fieldId + "_from");
+ if (this.#fromField === null) {
+ throw new Error("Unknown field with id '" + this._fieldId + "'.");
+ }
+
+ this.#toField = document.getElementById(this._fieldId + "_to");
+ if (this.#toField === null) {
+ throw new Error("Unknown field with id '" + this._fieldId + "'.");
+ }
+ }
+
+ protected _getData(): FormBuilderData {
+ return {
+ [this._fieldId]: {
+ from: DatePicker.getValue(this.#fromField as HTMLInputElement),
+ to: DatePicker.getValue(this.#toField as HTMLInputElement),
+ },
+ };
+ }
+
+ protected _readField(): void {}
+}
+
+export = DateRange;
--- /dev/null
+define(["require", "exports", "tslib", "./Field", "../../../Date/Picker"], function (require, exports, tslib_1, Field_1, Picker_1) {
+ "use strict";
+ Field_1 = tslib_1.__importDefault(Field_1);
+ Picker_1 = tslib_1.__importDefault(Picker_1);
+ class DateRange extends Field_1.default {
+ #fromField;
+ #toField;
+ constructor(fieldId) {
+ super(fieldId);
+ this.#fromField = document.getElementById(this._fieldId + "_from");
+ if (this.#fromField === null) {
+ throw new Error("Unknown field with id '" + this._fieldId + "'.");
+ }
+ this.#toField = document.getElementById(this._fieldId + "_to");
+ if (this.#toField === null) {
+ throw new Error("Unknown field with id '" + this._fieldId + "'.");
+ }
+ }
+ _getData() {
+ return {
+ [this._fieldId]: {
+ from: Picker_1.default.getValue(this.#fromField),
+ to: Picker_1.default.getValue(this.#toField),
+ },
+ };
+ }
+ _readField() { }
+ }
+ return DateRange;
+});
--- /dev/null
+<?php
+
+namespace wcf\system\form\builder\field;
+
+use wcf\system\form\builder\field\validation\FormFieldValidationError;
+
+/**
+ * Implementation of a form field for a date range (with a time).
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.2
+ */
+class DateRangeFormField extends AbstractFormField implements
+ IAttributeFormField,
+ IAutoFocusFormField,
+ ICssClassFormField,
+ IImmutableFormField,
+ INullableFormField
+{
+ use TAttributeFormField;
+ use TAutoFocusFormField;
+ use TCssClassFormField;
+ use TImmutableFormField;
+ use TNullableFormField;
+
+ /**
+ * is `true` if not only the date, but also the time can be set
+ * @var bool
+ */
+ protected $supportsTime = false;
+
+ /**
+ * @inheritDoc
+ */
+ protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/DateRange';
+
+ /**
+ * @inheritDoc
+ */
+ protected $templateName = 'shared_dateRangeFormField';
+
+ const DATE_FORMAT = 'Y-m-d';
+
+ const TIME_FORMAT = 'Y-m-d\TH:i:sP';
+
+ /**
+ * @inheritDoc
+ */
+ public function getSaveValue()
+ {
+ if ($this->getValue() === null && $this->isNullable()) {
+ return null;
+ }
+
+ return $this->getFromValue() . ';' . $this->getToValue();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function readValue()
+ {
+ if (
+ $this->getDocument()->hasRequestData($this->getPrefixedId())
+ && \is_array($this->getDocument()->getRequestData($this->getPrefixedId()))
+ ) {
+ $this->value = $this->getDocument()->getRequestData($this->getPrefixedId());
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets if not only the date, but also the time can be set.
+ */
+ public function supportTime($supportsTime = true): static
+ {
+ if ($this->value !== null) {
+ throw new \BadFunctionCallException(
+ "After a value has been set, time support cannot be changed for field '{$this->getId()}'."
+ );
+ }
+
+ $this->supportsTime = $supportsTime;
+
+ return $this;
+ }
+
+ /**
+ * Returns `true` if not only the date, but also the time can be set, and
+ * returns `false` otherwise.
+ *
+ * By default, the time cannot be set.
+ */
+ public function supportsTime(): bool
+ {
+ return $this->supportsTime;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validate()
+ {
+ if ($this->isRequired() && (!$this->getFromValue() || !$this->getToValue())) {
+ $this->addValidationError(new FormFieldValidationError('empty'));
+ }
+
+ if ($this->getFromValue()) {
+ $dateTime = \DateTime::createFromFormat(
+ $this->supportsTime() ? self::TIME_FORMAT : self::DATE_FORMAT,
+ $this->getFromValue()
+ );
+ if ($dateTime === false) {
+ $this->addValidationError(new FormFieldValidationError('invalid'));
+ }
+ }
+
+ if ($this->getToValue()) {
+ $dateTime = \DateTime::createFromFormat(
+ $this->supportsTime() ? self::TIME_FORMAT : self::DATE_FORMAT,
+ $this->getToValue()
+ );
+ if ($dateTime === false) {
+ $this->addValidationError(new FormFieldValidationError('invalid'));
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function value($value)
+ {
+ $values = \explode(';', $value);
+ if (\count($values) !== 2) {
+ throw new \InvalidArgumentException(
+ "Given value does not match format for field '{$this->getId()}'."
+ );
+ }
+
+ $this->value = [
+ 'from' => $values[0],
+ 'to' => $values[1],
+ ];
+
+ return $this;
+ }
+
+ public function getFromValue(): string
+ {
+ return $this->value['from'] ?? '';
+ }
+
+ public function getToValue(): string
+ {
+ return $this->value['to'] ?? '';
+ }
+}