Skip to content

Commit 5694559

Browse files
authored
fix(ui5-date-range-picker): remove relative date handling (#13365)
## Overview Currently our `ui5-daterange-picker` resolves relative dates like "today" and "tomorrow" to concrete dates. The `sap.m.DateRangeSelection` does not. It sets an error state instead, meaning that both project handles same scenarios in different ways causing inconsistencies. So in order to fix this we: - Added roundtrip check to detect relative date strings in `DateRangePicker` - Modified `isValid()`, `isValidValue()`, `isValidDisplayValue()` to reject them ## What This Fixes - Relative dates ("today", "tomorrow", etc.) now set `valueState="Negative"` instead of resolving - Behavior aligns with `sap.m.DateRangeSelection` - `DatePicker` continues resolving relative dates as before
1 parent fec44d7 commit 5694559

2 files changed

Lines changed: 94 additions & 18 deletions

File tree

packages/main/cypress/specs/DateRangePicker.cy.tsx

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -970,11 +970,52 @@ describe("Validation inside a form", () => {
970970
});
971971
});
972972

973+
describe("DateRangePicker rejects relative dates", () => {
974+
const relativeKeywords = ["today", "tomorrow", "yesterday"];
975+
976+
relativeKeywords.forEach(keyword => {
977+
it(`typing '${keyword}' sets error state`, () => {
978+
cy.mount(<DateRangePicker></DateRangePicker>);
979+
980+
cy.get("[ui5-daterange-picker]")
981+
.as("dateRangePicker")
982+
.shadow()
983+
.find("[ui5-datetime-input]")
984+
.realClick()
985+
.should("be.focused");
986+
987+
cy.realType(keyword);
988+
cy.realPress("Enter");
989+
990+
cy.get("@dateRangePicker")
991+
.should("have.value", keyword)
992+
.should("have.attr", "value-state", "Negative");
993+
});
994+
});
995+
996+
it("valid concrete date range does not set error state", () => {
997+
cy.mount(<DateRangePicker displayFormat="dd/MM/yyyy"></DateRangePicker>);
998+
999+
cy.get("[ui5-daterange-picker]")
1000+
.as("dateRangePicker")
1001+
.shadow()
1002+
.find("[ui5-datetime-input]")
1003+
.realClick()
1004+
.should("be.focused");
1005+
1006+
cy.realType("09/09/2020 - 10/10/2020");
1007+
cy.realPress("Enter");
1008+
1009+
cy.get("@dateRangePicker")
1010+
.should("have.attr", "value-state", "None");
1011+
});
1012+
});
1013+
9731014
describe("DateRangePicker - Two Calendars Feature", () => {
9741015
describe("Basic Two Calendars Display", () => {
9751016
it("should display two calendars when showTwoMonths is true", () => {
9761017
cy.mount(
977-
<DateRangePicker showTwoMonths={true} value="Jan 1, 2024 - Jan 31, 2024" />
1018+
<DateRangePicker showTwoMonths={true} value="2024-01-01 - 2024-01-31" />
9781019
);
9791020

9801021
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -993,7 +1034,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
9931034

9941035
it("should display one calendar when showTwoMonths is false", () => {
9951036
cy.mount(
996-
<DateRangePicker showTwoMonths={false} value="Jan 1, 2024 - Jan 31, 2024" />
1037+
<DateRangePicker showTwoMonths={false} value="2024-01-01 - 2024-01-31" />
9971038
);
9981039

9991040
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -1015,7 +1056,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
10151056

10161057
it("should show consecutive months in two calendars mode", () => {
10171058
cy.mount(
1018-
<DateRangePicker showTwoMonths={true} value="Mar 15, 2024 - Mar 20, 2024" />
1059+
<DateRangePicker showTwoMonths={true} value="2024-03-15 - 2024-03-20" />
10191060
);
10201061

10211062
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -1040,7 +1081,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
10401081

10411082
it("should dynamically toggle showTwoMonths after initial render", () => {
10421083
cy.mount(
1043-
<DateRangePicker value="Jan 15, 2024 - Jan 20, 2024" />
1084+
<DateRangePicker value="2024-01-15 - 2024-01-20" />
10441085
);
10451086

10461087
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -1143,7 +1184,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
11431184

11441185
it("should highlight selection across both calendars", () => {
11451186
cy.mount(
1146-
<DateRangePicker showTwoMonths={true} value="Jan 20, 2024 - Feb 10, 2024" />
1187+
<DateRangePicker showTwoMonths={true} value="2024-01-20 - 2024-02-10" />
11471188
);
11481189

11491190
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -1161,7 +1202,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
11611202

11621203
it("should update value when selecting new range", () => {
11631204
cy.mount(
1164-
<DateRangePicker showTwoMonths={true} value="Jan 1, 2024 - Jan 5, 2024" />
1205+
<DateRangePicker showTwoMonths={true} value="2024-01-01 - 2024-01-05" />
11651206
);
11661207

11671208
const changeSpy = cy.spy().as("changeSpy");
@@ -1186,8 +1227,8 @@ describe("DateRangePicker - Two Calendars Feature", () => {
11861227

11871228
it("should respect min/max date constraints with two calendars", () => {
11881229
cy.mount(
1189-
<DateRangePicker
1190-
showTwoMonths={true}
1230+
<DateRangePicker
1231+
showTwoMonths={true}
11911232
formatPattern="dd/MM/yyyy"
11921233
minDate="10/01/2024"
11931234
maxDate="28/02/2024"
@@ -1224,7 +1265,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
12241265
describe("Navigation in Two Calendars Mode", () => {
12251266
it("should navigate both calendars forward", () => {
12261267
cy.mount(
1227-
<DateRangePicker showTwoMonths={true} value="Jan 15, 2024 - Jan 20, 2024" />
1268+
<DateRangePicker showTwoMonths={true} value="2024-01-15 - 2024-01-20" />
12281269
);
12291270

12301271
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -1245,7 +1286,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
12451286

12461287
it("should navigate both calendars backward", () => {
12471288
cy.mount(
1248-
<DateRangePicker showTwoMonths={true} value="Mar 15, 2024 - Mar 20, 2024" />
1289+
<DateRangePicker showTwoMonths={true} value="2024-03-15 - 2024-03-20" />
12491290
);
12501291

12511292
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -1268,7 +1309,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
12681309
describe("Picker Overlays", () => {
12691310
it("should show month picker overlay when clicking month button", () => {
12701311
cy.mount(
1271-
<DateRangePicker showTwoMonths={true} value="Jan 15, 2024 - Jan 20, 2024" />
1312+
<DateRangePicker showTwoMonths={true} value="2024-01-15 - 2024-01-20" />
12721313
);
12731314

12741315
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -1297,7 +1338,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
12971338

12981339
it("should show year picker overlay when clicking year button", () => {
12991340
cy.mount(
1300-
<DateRangePicker showTwoMonths={true} value="Jan 15, 2024 - Jan 20, 2024" />
1341+
<DateRangePicker showTwoMonths={true} value="2024-01-15 - 2024-01-20" />
13011342
);
13021343

13031344
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -1319,7 +1360,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
13191360

13201361
it("should return to day pickers after selecting from month picker", () => {
13211362
cy.mount(
1322-
<DateRangePicker showTwoMonths={true} value="Jan 15, 2024 - Jan 20, 2024" />
1363+
<DateRangePicker showTwoMonths={true} value="2024-01-15 - 2024-01-20" />
13231364
);
13241365

13251366
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -1356,7 +1397,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
13561397
describe("Keyboard Navigation", () => {
13571398
it("should allow keyboard navigation through header buttons", () => {
13581399
cy.mount(
1359-
<DateRangePicker showTwoMonths={true} value="Jan 15, 2024 - Jan 20, 2024" />
1400+
<DateRangePicker showTwoMonths={true} value="2024-01-15 - 2024-01-20" />
13601401
);
13611402

13621403
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -1413,7 +1454,7 @@ describe("DateRangePicker - Two Calendars Feature", () => {
14131454
describe("Edge Cases", () => {
14141455
it("should handle year boundary correctly", () => {
14151456
cy.mount(
1416-
<DateRangePicker showTwoMonths={true} value="Dec 15, 2025 - Dec 20, 2025" />
1457+
<DateRangePicker showTwoMonths={true} value="2025-12-15 - 2025-12-20" />
14171458
);
14181459

14191460
cy.get<DateRangePicker>("[ui5-daterange-picker]")
@@ -1447,5 +1488,3 @@ describe("DateRangePicker - Two Calendars Feature", () => {
14471488
});
14481489
});
14491490
});
1450-
1451-

packages/main/src/DateRangePicker.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDat
66
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";
77
import modifyDateBy from "@ui5/webcomponents-localization/dist/dates/modifyDateBy.js";
88
import getTodayUTCTimestamp from "@ui5/webcomponents-localization/dist/dates/getTodayUTCTimestamp.js";
9+
import type DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js";
910
import {
1011
DATERANGE_DESCRIPTION,
1112
DATERANGEPICKER_POPOVER_ACCESSIBLE_NAME,
@@ -42,7 +43,10 @@ const DEFAULT_DELIMITER = "-";
4243
* ### Usage
4344
* The user can enter a date by:
4445
* Using the calendar that opens in a popup or typing it in directly in the input field (not available for mobile devices).
45-
* For the `ui5-daterange-picker`
46+
* For the `ui5-daterange-picker`:
47+
*
48+
* **Note:** Relative date values such as "today", "yesterday", or "tomorrow" are not supported.
49+
* Entering a relative date sets the component to an error state.
4650
* ### ES6 Module Import
4751
*
4852
* `import "@ui5/webcomponents/dist/DateRangePicker.js";`
@@ -155,6 +159,27 @@ class DateRangePicker extends DatePicker implements IFormInputElement {
155159
this._prevDelimiter = null;
156160
}
157161

162+
/**
163+
* Checks if a date string is a relative date (e.g. "today", "tomorrow")
164+
* that would be resolved by DateFormat.parseRelative().
165+
* Relative dates are not supported in DateRangePicker.
166+
* @private
167+
*/
168+
_isRelativeValue(dateString: string, format: DateFormat): boolean {
169+
const trimmed = dateString.trim();
170+
if (!trimmed) {
171+
return false;
172+
}
173+
174+
const parsed = format.parse(trimmed);
175+
if (!parsed) {
176+
return false;
177+
}
178+
179+
const formatted = format.format(parsed);
180+
return formatted !== trimmed;
181+
}
182+
158183
/**
159184
* **Note:** The getter method is inherited and not supported. If called it will return an empty value.
160185
* @public
@@ -329,6 +354,10 @@ class DateRangePicker extends DatePicker implements IFormInputElement {
329354
isValid(value: string): boolean {
330355
const parts = this._splitValueByDelimiter(value).filter(str => str.trim() !== "");
331356

357+
if (parts.some(dateString => this._isRelativeValue(dateString, this.getFormat()))) {
358+
return false;
359+
}
360+
332361
return parts.length <= 2 && parts.every(dateString => super.isValid(dateString)); // must be at most 2 dates and each must be valid
333362
}
334363

@@ -340,6 +369,10 @@ class DateRangePicker extends DatePicker implements IFormInputElement {
340369
isValidValue(value: string): boolean {
341370
const parts = this._splitValueByDelimiter(value).filter(str => str.trim() !== "");
342371

372+
if (parts.some(dateString => this._isRelativeValue(dateString, this.getValueFormat()))) {
373+
return false;
374+
}
375+
343376
return parts.length <= 2 && parts.every(dateString => super.isValidValue(dateString)); // must be at most 2 dates and each must be valid
344377
}
345378

@@ -351,6 +384,10 @@ class DateRangePicker extends DatePicker implements IFormInputElement {
351384
isValidDisplayValue(value: string): boolean {
352385
const parts = this._splitValueByDelimiter(value).filter(str => str.trim() !== "");
353386

387+
if (parts.some(dateString => this._isRelativeValue(dateString, this.getDisplayFormat()))) {
388+
return false;
389+
}
390+
354391
return parts.length <= 2 && parts.every(dateString => super.isValidDisplayValue(dateString)); // must be at most 2 dates and each must be valid
355392
}
356393

0 commit comments

Comments
 (0)