diff --git a/docs/DataStructure.md b/docs/DataStructure.md
index 29588473d..f1d1361db 100644
--- a/docs/DataStructure.md
+++ b/docs/DataStructure.md
@@ -229,3 +229,6 @@ Optional extra settings for some [Question Types](#question-types)
| `dateMax` | `date` | Integer | - | Maximum allowed date to be chosen (as Unix timestamp) |
| `dateMin` | `date` | Integer | - | Minimum allowed date to be chosen (as Unix timestamp) |
| `dateRange` | `date` | Boolean | `true/false` | The date picker should query a date range |
+| `timeMin` | `time` | Integer | - | Maximum allowed time to be chosen (as `HH:mm` string) |
+| `timeMax` | `time` | Integer | - | Minimum allowed time to be chosen (as `HH:mm` string) |
+| `timeRange` | `time` | Boolean | `true/false` | The time picker should query a time range |
diff --git a/img/clock_arrow_down.svg b/img/clock_arrow_down.svg
new file mode 100644
index 000000000..d761ef15c
--- /dev/null
+++ b/img/clock_arrow_down.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/img/clock_arrow_up.svg b/img/clock_arrow_up.svg
new file mode 100644
index 000000000..b6dd1b58c
--- /dev/null
+++ b/img/clock_arrow_up.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/Constants.php b/lib/Constants.php
index cdee36930..a18ebc2e8 100644
--- a/lib/Constants.php
+++ b/lib/Constants.php
@@ -153,6 +153,12 @@ class Constants {
'dateRange' => ['boolean', 'NULL'],
];
+ public const EXTRA_SETTINGS_TIME = [
+ 'timeMax' => ['string', 'NULL'],
+ 'timeMin' => ['string', 'NULL'],
+ 'timeRange' => ['boolean', 'NULL'],
+ ];
+
// should be in sync with FileTypes.js
public const EXTRA_SETTINGS_ALLOWED_FILE_TYPES = [
'image',
diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php
index c87c0d274..d92124aca 100644
--- a/lib/ResponseDefinitions.php
+++ b/lib/ResponseDefinitions.php
@@ -31,6 +31,9 @@
* optionsLimitMax?: int,
* optionsLimitMin?: int,
* shuffleOptions?: bool,
+ * timeMax?: string,
+ * timeMin?: string,
+ * timeRange?: bool,
* validationRegex?: string,
* validationType?: string
* }
diff --git a/lib/Service/FormsService.php b/lib/Service/FormsService.php
index db0a547b5..12e268b62 100644
--- a/lib/Service/FormsService.php
+++ b/lib/Service/FormsService.php
@@ -632,6 +632,9 @@ public function areExtraSettingsValid(array $extraSettings, string $questionType
case Constants::ANSWER_TYPE_DATE:
$allowed = Constants::EXTRA_SETTINGS_DATE;
break;
+ case Constants::ANSWER_TYPE_TIME:
+ $allowed = Constants::EXTRA_SETTINGS_TIME;
+ break;
default:
$allowed = [];
}
@@ -656,6 +659,12 @@ public function areExtraSettingsValid(array $extraSettings, string $questionType
&& $extraSettings['dateMin'] > $extraSettings['dateMax']) {
return false;
}
+ } elseif ($questionType === Constants::ANSWER_TYPE_TIME) {
+ // Ensure timeMin and timeMax don't overlap
+ if (isset($extraSettings['timeMin']) && isset($extraSettings['timeMax'])
+ && $extraSettings['timeMin'] > $extraSettings['timeMax']) {
+ return false;
+ }
} elseif ($questionType === Constants::ANSWER_TYPE_MULTIPLE) {
// Ensure limits are sane
if (isset($extraSettings['optionsLimitMax']) && isset($extraSettings['optionsLimitMin'])
diff --git a/lib/Service/SubmissionService.php b/lib/Service/SubmissionService.php
index baea59590..7c5190251 100644
--- a/lib/Service/SubmissionService.php
+++ b/lib/Service/SubmissionService.php
@@ -457,15 +457,17 @@ private function validateDateTime(array $answers, string $format, ?string $text
}
if ($previousDate !== null && $d < $previousDate) {
- throw new \InvalidArgumentException(sprintf('Dates for question "%s" must be in ascending order.', $text));
+ throw new \InvalidArgumentException(sprintf('Dates/times for question "%s" must be in ascending order.', $text));
}
$previousDate = $d;
if ($extraSettings) {
if ((isset($extraSettings['dateMin']) && $d < (new DateTime())->setTimestamp($extraSettings['dateMin'])) ||
- (isset($extraSettings['dateMax']) && $d > (new DateTime())->setTimestamp($extraSettings['dateMax']))
+ (isset($extraSettings['dateMax']) && $d > (new DateTime())->setTimestamp($extraSettings['dateMax'])) ||
+ (isset($extraSettings['timeMin']) && $d > (new DateTime())->setTimestamp($extraSettings['timeMin'])) ||
+ (isset($extraSettings['timeMax']) && $d > (new DateTime())->setTimestamp($extraSettings['timeMax']))
) {
- throw new \InvalidArgumentException(sprintf('Date is not in the allowed range for question "%s".', $text));
+ throw new \InvalidArgumentException(sprintf('Date/time is not in the allowed range for question "%s".', $text));
}
}
}
diff --git a/openapi.json b/openapi.json
index 0dc887668..17f99de10 100644
--- a/openapi.json
+++ b/openapi.json
@@ -448,6 +448,15 @@
"shuffleOptions": {
"type": "boolean"
},
+ "timeMax": {
+ "type": "string"
+ },
+ "timeMin": {
+ "type": "string"
+ },
+ "timeRange": {
+ "type": "boolean"
+ },
"validationRegex": {
"type": "string"
},
diff --git a/src/components/Questions/QuestionDate.vue b/src/components/Questions/QuestionDate.vue
index ea0ff51a8..65bc4977a 100644
--- a/src/components/Questions/QuestionDate.vue
+++ b/src/components/Questions/QuestionDate.vue
@@ -42,6 +42,39 @@
+
+