diff --git a/addons/web/static/src/core/datetime/datetime_picker.js b/addons/web/static/src/core/datetime/datetime_picker.js
index 0edfa5c5e1b..c5a5013ff13 100644
--- a/addons/web/static/src/core/datetime/datetime_picker.js
+++ b/addons/web/static/src/core/datetime/datetime_picker.js
@@ -37,7 +37,8 @@ import { ensureArray } from "../utils/arrays";
* @property {PrecisionLevel} [minPrecision="days"]
* @property {(value: DateTime) => any} [onSelect]
* @property {boolean} [range]
- * @property {number} [rounding=5]
+ * @property {number} [rounding=5] the rounding in minutes, pass 0 to show seconds, pass 1 to avoid
+ * rounding minutes without displaying seconds.
* @property {{ buttons?: any }} [slots]
* @property {"date" | "datetime"} [type]
* @property {NullableDateTime | NullableDateRange} value
@@ -143,6 +144,7 @@ const toWeekItem = (weekDayItems) => ({
// Time constants
const HOURS = numberRange(0, 24).map((hour) => [hour, String(hour)]);
const MINUTES = numberRange(0, 60).map((minute) => [minute, String(minute || 0).padStart(2, "0")]);
+const SECONDS = [...MINUTES];
const MERIDIEMS = ["AM", "PM"];
/**
@@ -380,14 +382,19 @@ export class DateTimePicker extends Component {
);
this.availableHours = HOURS;
this.availableMinutes = MINUTES.filter((minute) => !(minute[0] % props.rounding));
+ this.availableSeconds = props.rounding ? [] : SECONDS;
this.allowedPrecisionLevels = this.filterPrecisionLevels(
props.minPrecision,
props.maxPrecision
);
this.additionalMonth = props.range && !this.env.isSmall;
- this.maxDate = parseLimitDate(props.maxDate, MAX_VALID_DATE).endOf("day");
- this.minDate = parseLimitDate(props.minDate, MIN_VALID_DATE).startOf("day");
+ this.maxDate = parseLimitDate(props.maxDate, MAX_VALID_DATE);
+ this.minDate = parseLimitDate(props.minDate, MIN_VALID_DATE);
+ if (this.props.type === "date") {
+ this.maxDate = this.maxDate.endOf("day");
+ this.minDate = this.minDate.startOf("day");
+ }
if (this.maxDate < this.minDate) {
throw new Error(`DateTimePicker error: given "maxDate" comes before "minDate".`);
diff --git a/addons/web/static/src/core/datetime/datetime_picker.xml b/addons/web/static/src/core/datetime/datetime_picker.xml
index 4d99bb29b0c..63555eeda9a 100644
--- a/addons/web/static/src/core/datetime/datetime_picker.xml
+++ b/addons/web/static/src/core/datetime/datetime_picker.xml
@@ -66,6 +66,13 @@
+
+ :
+
+
+
+
+
diff --git a/addons/web/static/tests/core/datetime/datetime_picker_tests.js b/addons/web/static/tests/core/datetime/datetime_picker_tests.js
index 4710c4dcb3b..9968bce95cd 100644
--- a/addons/web/static/tests/core/datetime/datetime_picker_tests.js
+++ b/addons/web/static/tests/core/datetime/datetime_picker_tests.js
@@ -872,6 +872,23 @@ QUnit.module("Components", ({ beforeEach }) => {
);
});
+ QUnit.test("rounding=0 enables seconds picker", async (assert) => {
+ await mountPicker({
+ rounding: 0,
+ });
+
+ const [hourSelect, minuteSelect, secondsSelect] = getTimePickers().at(0);
+ assert.deepEqual(getTexts(hourSelect, "option"), range(24, String));
+ assert.deepEqual(
+ getTexts(minuteSelect, "option"),
+ range(60, (i) => pad2(i))
+ );
+ assert.deepEqual(
+ getTexts(secondsSelect, "option"),
+ range(60, (i) => pad2(i))
+ );
+ });
+
QUnit.test("no value, select date without handler", async (assert) => {
await mountPicker();
@@ -903,6 +920,86 @@ QUnit.module("Components", ({ beforeEach }) => {
assert.verifySteps(["2023-04-25T18:00:00", "2023-04-25T18:05:00"]);
});
+ QUnit.test("minDate with time: selecting out-of-range and in-range times", async (assert) => {
+ await mountPicker({
+ onSelect: (value) => assert.step(formatForStep(value)),
+ minDate: DateTime.fromISO("2023-04-25T16:00:00.000"),
+ });
+
+ const [hourSelect] = getTimePickers().at(0);
+ await editSelect(hourSelect, null, "15");
+ assert.verifySteps([]);
+ await editSelect(hourSelect, null, "16");
+ assert.verifySteps(["2023-04-25T16:00:00"]);
+ });
+
+ QUnit.test("maxDate with time: selecting out-of-range and in-range times", async (assert) => {
+ await mountPicker({
+ onSelect: (value) => assert.step(formatForStep(value)),
+ maxDate: DateTime.fromISO("2023-04-25T16:00:00.000"),
+ });
+
+ const [hourSelect] = getTimePickers().at(0);
+ await editSelect(hourSelect, null, "17");
+ assert.verifySteps([]);
+ await editSelect(hourSelect, null, "16");
+ assert.verifySteps(["2023-04-25T16:00:00"]);
+ });
+
+ QUnit.test(
+ "max and min date with time: selecting out-of-range and in-range times",
+ async (assert) => {
+ await mountPicker({
+ onSelect: (value) => assert.step(formatForStep(value)),
+ minDate: DateTime.fromISO("2023-04-25T16:00:00.000"),
+ maxDate: DateTime.fromISO("2023-04-25T16:00:00.000"),
+ });
+
+ const [hourSelect] = getTimePickers().at(0);
+ await editSelect(hourSelect, null, "15");
+ await editSelect(hourSelect, null, "17");
+ assert.verifySteps([]);
+ await editSelect(hourSelect, null, "16");
+ assert.verifySteps(["2023-04-25T16:00:00"]);
+ }
+ );
+
+ QUnit.test(
+ "max and min date with time: selecting invalid minutes and making it valid by selecting hours",
+ async (assert) => {
+ await mountPicker({
+ onSelect: (value) => assert.step(formatForStep(value)),
+ minDate: DateTime.fromISO("2023-04-25T16:10:00.000"),
+ maxDate: DateTime.fromISO("2023-04-25T16:50:00.000"),
+ });
+
+ const [hourSelect, minuteSelect] = getTimePickers().at(0);
+ await editSelect(hourSelect, null, "13");
+ await editSelect(minuteSelect, null, "30");
+ assert.verifySteps([]);
+ await editSelect(hourSelect, null, "16");
+ assert.verifySteps(["2023-04-25T16:30:00"]);
+ }
+ );
+
+ QUnit.test(
+ "max and min date with time: valid time on invalid day becomes valid when selecting day",
+ async (assert) => {
+ await mountPicker({
+ onSelect: (value) => assert.step(formatForStep(value)),
+ minDate: DateTime.fromISO("2023-04-24T16:10:00.000"),
+ maxDate: DateTime.fromISO("2023-04-24T16:50:00.000"),
+ });
+
+ const [hourSelect, minuteSelect] = getTimePickers().at(0);
+ await editSelect(hourSelect, null, "16");
+ await editSelect(minuteSelect, null, "30");
+ assert.verifySteps([]);
+ await click(getPickerCell("24"));
+ assert.verifySteps(["2023-04-24T16:30:00"]);
+ }
+ );
+
QUnit.test("single value, select date", async (assert) => {
await mountPicker({
value: DateTime.fromObject({ day: 30, hour: 8, minute: 43 }),