Skip to content

Commit 9c7c64e

Browse files
committed
fix: update dateMin and dateMax to accept string or integer formats and enhance validation logic
Signed-off-by: Christian Hartmann <chris-hartmann@gmx.de>
1 parent 8b0dbca commit 9c7c64e

8 files changed

Lines changed: 139 additions & 25 deletions

File tree

docs/DataStructure.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,8 @@ Optional extra settings for some [Question Types](#question-types)
255255
| `allowedFileExtensions` | `file` | Array of strings | `'jpg', 'png'` | Allowed file extensions for file upload |
256256
| `maxAllowedFilesCount` | `file` | Integer | - | Maximum number of files that can be uploaded, 0 means no limit |
257257
| `maxFileSize` | `file` | Integer | - | Maximum file size in bytes, 0 means no limit |
258-
| `dateMax` | `date` | Integer | - | Maximum allowed date to be chosen (as Unix timestamp) |
259-
| `dateMin` | `date` | Integer | - | Minimum allowed date to be chosen (as Unix timestamp) |
258+
| `dateMax` | `date` | string / Integer | - | Maximum allowed date to be chosen (as `YYYY-MM-DD` string, or Unix timestamp) |
259+
| `dateMin` | `date` | string / Integer | - | Minimum allowed date to be chosen (as `YYYY-MM-DD` string, or Unix timestamp) |
260260
| `dateRange` | `date` | Boolean | `true/false` | The date picker should query a date range |
261261
| `timeMax` | `time` | string | - | Maximum allowed time to be chosen (as `HH:mm` string) |
262262
| `timeMin` | `time` | string | - | Minimum allowed time to be chosen (as `HH:mm` string) |

lib/Constants.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ class Constants {
187187
];
188188

189189
public const EXTRA_SETTINGS_DATE = [
190-
'dateMax' => ['integer', 'NULL'],
191-
'dateMin' => ['integer', 'NULL'],
190+
'dateMax' => ['string', 'integer', 'NULL'],
191+
'dateMin' => ['string', 'integer', 'NULL'],
192192
'dateRange' => ['boolean', 'NULL'],
193193
];
194194

lib/ResponseDefinitions.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
* allowOtherAnswer?: bool,
2525
* allowedFileExtensions?: list<string>,
2626
* allowedFileTypes?: list<string>,
27-
* dateMax?: int,
28-
* dateMin?: int,
27+
* dateMax?: string|int,
28+
* dateMin?: string|int,
2929
* dateRange?: bool,
3030
* maxAllowedFilesCount?: int,
3131
* maxFileSize?: int,
@@ -36,8 +36,8 @@
3636
* optionsLimitMin?: int,
3737
* optionsLowest?: 0|1,
3838
* shuffleOptions?: bool,
39-
* timeMax?: int,
40-
* timeMin?: int,
39+
* timeMax?: string,
40+
* timeMin?: string,
4141
* timeRange?: bool,
4242
* validationRegex?: string,
4343
* validationType?: string,

lib/Service/FormsService.php

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -861,9 +861,38 @@ public function areExtraSettingsValid(array $extraSettings, string $questionType
861861

862862
// Validate extraSettings for specific question types
863863
if ($questionType === Constants::ANSWER_TYPE_DATE) {
864+
$format = Constants::ANSWER_PHPDATETIME_FORMAT['date'];
865+
$dateMinDate = null;
866+
$dateMaxDate = null;
867+
868+
// Validate dateMin format
869+
if (isset($extraSettings['dateMin'])) {
870+
if (is_int($extraSettings['dateMin'])) {
871+
$dateMinDate = (new \DateTime())->setTimestamp($extraSettings['dateMin'])->setTime(0, 0, 0);
872+
} else {
873+
$dateMinString = $extraSettings['dateMin'];
874+
$dateMinDate = \DateTime::createFromFormat('!' . $format, $dateMinString);
875+
if (!$dateMinDate || $dateMinDate->format($format) !== $dateMinString) {
876+
return false;
877+
}
878+
}
879+
}
880+
881+
// Validate dateMax format
882+
if (isset($extraSettings['dateMax'])) {
883+
if (is_int($extraSettings['dateMax'])) {
884+
$dateMaxDate = (new \DateTime())->setTimestamp($extraSettings['dateMax'])->setTime(0, 0, 0);
885+
} else {
886+
$dateMaxString = $extraSettings['dateMax'];
887+
$dateMaxDate = \DateTime::createFromFormat('!' . $format, $dateMaxString);
888+
if (!$dateMaxDate || $dateMaxDate->format($format) !== $dateMaxString) {
889+
return false;
890+
}
891+
}
892+
}
893+
864894
// Ensure dateMin and dateMax don't overlap
865-
if (isset($extraSettings['dateMin']) && isset($extraSettings['dateMax'])
866-
&& $extraSettings['dateMin'] > $extraSettings['dateMax']) {
895+
if ($dateMinDate !== null && $dateMaxDate !== null && $dateMinDate > $dateMaxDate) {
867896
return false;
868897
}
869898
} elseif ($questionType === Constants::ANSWER_TYPE_TIME) {

lib/Service/SubmissionService.php

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -752,10 +752,31 @@ private function validateDateTime(array $answers, string $format, ?string $text
752752
$previousDate = $d;
753753

754754
if ($extraSettings) {
755-
if ((isset($extraSettings['dateMin']) && $d < (new DateTime())->setTimestamp($extraSettings['dateMin']))
756-
|| (isset($extraSettings['dateMax']) && $d > (new DateTime())->setTimestamp($extraSettings['dateMax']))
757-
|| (isset($extraSettings['timeMin']) && $d < DateTime::createFromFormat($format, $extraSettings['timeMin']))
758-
|| (isset($extraSettings['timeMax']) && $d > DateTime::createFromFormat($format, $extraSettings['timeMax']))
755+
$dateMin = isset($extraSettings['dateMin'])
756+
? (is_int($extraSettings['dateMin'])
757+
? (new DateTime())->setTimestamp($extraSettings['dateMin'])->setTime(0, 0, 0)
758+
: DateTime::createFromFormat('!' . $format, $extraSettings['dateMin']))
759+
: null;
760+
$dateMax = isset($extraSettings['dateMax'])
761+
? (is_int($extraSettings['dateMax'])
762+
? (new DateTime())->setTimestamp($extraSettings['dateMax'])->setTime(0, 0, 0)
763+
: DateTime::createFromFormat('!' . $format, $extraSettings['dateMax']))
764+
: null;
765+
$timeMin = isset($extraSettings['timeMin'])
766+
? DateTime::createFromFormat($format, $extraSettings['timeMin'])
767+
: null;
768+
$timeMax = isset($extraSettings['timeMax'])
769+
? DateTime::createFromFormat($format, $extraSettings['timeMax'])
770+
: null;
771+
772+
$compareDate = ($dateMin !== null || $dateMax !== null)
773+
? (DateTime::createFromFormat('!' . $format, $dateStr) ?: $d)
774+
: $d;
775+
776+
if (($dateMin !== null && $compareDate < $dateMin)
777+
|| ($dateMax !== null && $compareDate > $dateMax)
778+
|| ($timeMin !== null && $d < $timeMin)
779+
|| ($timeMax !== null && $d > $timeMax)
759780
) {
760781
throw new \InvalidArgumentException(sprintf('Date/time is not in the allowed range for question "%s".', $text));
761782
}

src/components/Questions/QuestionDate.vue

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ type PickerType =
132132
133133
type QuestionDateExtraSettings = {
134134
dateRange?: boolean
135-
dateMax?: number | null
136-
dateMin?: number | null
135+
dateMax?: string | number | null
136+
dateMin?: string | number | null
137137
timeRange?: boolean
138138
timeMax?: string | null
139139
timeMin?: string | null
@@ -183,11 +183,11 @@ export default defineComponent({
183183
/**
184184
* Form expires timestamp to Date of the datepicker
185185
*
186-
* @param value the expires timestamp
186+
* @param value the expires timestamp or formatted date string
187187
* @return
188188
*/
189-
const parseTimestampToDate = (value: number): Date => {
190-
return moment(value, 'X').toDate()
189+
const parseTimestampToDate = (value: string | number): Date => {
190+
return moment(value, [props.answerType.storageFormat, 'X']).toDate()
191191
}
192192
193193
/**
@@ -251,7 +251,10 @@ export default defineComponent({
251251
*/
252252
const dateMax = computed<Date | undefined>(() => {
253253
return extraSettings.value.dateMax
254-
? moment(extraSettings.value.dateMax, 'X').toDate()
254+
? moment(extraSettings.value.dateMax, [
255+
props.answerType.storageFormat,
256+
'X',
257+
]).toDate()
255258
: undefined
256259
})
257260
@@ -260,7 +263,10 @@ export default defineComponent({
260263
*/
261264
const dateMin = computed<Date | undefined>(() => {
262265
return extraSettings.value.dateMin
263-
? moment(extraSettings.value.dateMin, 'X').toDate()
266+
? moment(extraSettings.value.dateMin, [
267+
props.answerType.storageFormat,
268+
'X',
269+
]).toDate()
264270
: undefined
265271
})
266272
@@ -315,9 +321,12 @@ export default defineComponent({
315321
*
316322
* @param value - The new maximum date value. Can be a string or a Date object.
317323
*/
318-
const onDateMaxChange = (value: string | Date): void => {
324+
const onDateMaxChange = (value: string | Date | null): void => {
319325
question.onExtraSettingsChange({
320-
dateMax: parseInt(moment(value).format('X')),
326+
dateMax:
327+
value === null || value === ''
328+
? null
329+
: moment(value).format(props.answerType.storageFormat),
321330
})
322331
}
323332
@@ -327,9 +336,12 @@ export default defineComponent({
327336
*
328337
* @param value - The new minimum date value. Can be a string or a Date object.
329338
*/
330-
const onDateMinChange = (value: string | Date): void => {
339+
const onDateMinChange = (value: string | Date | null): void => {
331340
question.onExtraSettingsChange({
332-
dateMin: parseInt(moment(value).format('X')),
341+
dateMin:
342+
value === null || value === ''
343+
? null
344+
: moment(value).format(props.answerType.storageFormat),
333345
})
334346
}
335347

tests/Unit/Service/FormsServiceTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,6 +1528,14 @@ public static function dataAreExtraSettingsValid() {
15281528
'questionType' => Constants::ANSWER_TYPE_DATE,
15291529
'expected' => true
15301530
],
1531+
'valid-date-settings-string' => [
1532+
'extraSettings' => [
1533+
'dateMin' => '2026-08-20',
1534+
'dateMax' => '2026-08-25',
1535+
],
1536+
'questionType' => Constants::ANSWER_TYPE_DATE,
1537+
'expected' => true
1538+
],
15311539
'invalid-date-settings' => [
15321540
'extraSettings' => [
15331541
'dateMin' => 'today',
@@ -1536,6 +1544,14 @@ public static function dataAreExtraSettingsValid() {
15361544
'questionType' => Constants::ANSWER_TYPE_DATE,
15371545
'expected' => false
15381546
],
1547+
'invalid-date-settings-overlap' => [
1548+
'extraSettings' => [
1549+
'dateMin' => '2026-08-25',
1550+
'dateMax' => '2026-08-20',
1551+
],
1552+
'questionType' => Constants::ANSWER_TYPE_DATE,
1553+
'expected' => false
1554+
],
15391555
'invalid-date-limits' => [
15401556
// max < min
15411557
'extraSettings' => [

tests/Unit/Service/SubmissionServiceTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,42 @@ public static function dataValidateSubmission() {
10091009
// Expected Result
10101010
'Date/time is not in the allowed range for question "q1".',
10111011
],
1012+
'date-exact-limits-question-string' => [
1013+
// Questions
1014+
[
1015+
['id' => 1, 'type' => 'date', 'text' => 'q1', 'isRequired' => false, 'extraSettings' => ['dateMin' => '2026-08-20', 'dateMax' => '2026-08-24']]
1016+
],
1017+
// Answers
1018+
[
1019+
'1' => ['2026-08-24']
1020+
],
1021+
// Expected Result
1022+
null,
1023+
],
1024+
'date-below-min-question-string' => [
1025+
// Questions
1026+
[
1027+
['id' => 1, 'type' => 'date', 'text' => 'q1', 'isRequired' => false, 'extraSettings' => ['dateMin' => '2026-08-20', 'dateMax' => '2026-08-24']]
1028+
],
1029+
// Answers
1030+
[
1031+
'1' => ['2026-08-19']
1032+
],
1033+
// Expected Result
1034+
'Date/time is not in the allowed range for question "q1".',
1035+
],
1036+
'date-above-max-question-string' => [
1037+
// Questions
1038+
[
1039+
['id' => 1, 'type' => 'date', 'text' => 'q1', 'isRequired' => false, 'extraSettings' => ['dateMin' => '2026-08-20', 'dateMax' => '2026-08-24']]
1040+
],
1041+
// Answers
1042+
[
1043+
'1' => ['2026-08-25']
1044+
],
1045+
// Expected Result
1046+
'Date/time is not in the allowed range for question "q1".',
1047+
],
10121048
'valid-date-range' => [
10131049
// Questions
10141050
[

0 commit comments

Comments
 (0)