Skip to content

Commit 85af6ce

Browse files
authored
fix(datetime): only log out of bounds warning if value set (#25835)
resolves #25833
1 parent a1171e8 commit 85af6ce

File tree

4 files changed

+198
-29
lines changed

4 files changed

+198
-29
lines changed

core/src/components/datetime/datetime.tsx

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,15 @@ import {
4646
getPreviousYear,
4747
getStartOfWeek,
4848
} from './utils/manipulation';
49-
import { clampDate, convertToArrayOfNumbers, getPartsFromCalendarDay, parseAmPm, parseDate } from './utils/parse';
49+
import {
50+
clampDate,
51+
convertToArrayOfNumbers,
52+
getPartsFromCalendarDay,
53+
parseAmPm,
54+
parseDate,
55+
parseMaxParts,
56+
parseMinParts,
57+
} from './utils/parse';
5058
import {
5159
getCalendarDayState,
5260
isDayDisabled,
@@ -774,37 +782,24 @@ export class Datetime implements ComponentInterface {
774782
};
775783

776784
private processMinParts = () => {
777-
if (this.min === undefined) {
785+
const { min, todayParts } = this;
786+
if (min === undefined) {
778787
this.minParts = undefined;
779788
return;
780789
}
781790

782-
const { month, day, year, hour, minute } = parseDate(this.min);
783-
784-
this.minParts = {
785-
month,
786-
day,
787-
year,
788-
hour,
789-
minute,
790-
};
791+
this.minParts = parseMinParts(min, todayParts);
791792
};
792793

793794
private processMaxParts = () => {
794-
if (this.max === undefined) {
795+
const { max, todayParts } = this;
796+
797+
if (max === undefined) {
795798
this.maxParts = undefined;
796799
return;
797800
}
798801

799-
const { month, day, year, hour, minute } = parseDate(this.max);
800-
801-
this.maxParts = {
802-
month,
803-
day,
804-
year,
805-
hour,
806-
minute,
807-
};
802+
this.maxParts = parseMaxParts(max, todayParts);
808803
};
809804

810805
private initializeCalendarListener = () => {
@@ -1140,7 +1135,8 @@ export class Datetime implements ComponentInterface {
11401135
}
11411136

11421137
private processValue = (value?: string | string[] | null) => {
1143-
this.highlightActiveParts = !!value;
1138+
const hasValue = !!value;
1139+
this.highlightActiveParts = hasValue;
11441140
let valueToProcess = parseDate(value || getToday());
11451141

11461142
const { minParts, maxParts, multiple } = this;
@@ -1149,7 +1145,17 @@ export class Datetime implements ComponentInterface {
11491145
valueToProcess = (valueToProcess as DatetimeParts[])[0];
11501146
}
11511147

1152-
warnIfValueOutOfBounds(valueToProcess, minParts, maxParts);
1148+
/**
1149+
* Datetime should only warn of out of bounds values
1150+
* if set by the user. If the `value` is undefined,
1151+
* we will default to today's date which may be out
1152+
* of bounds. In this case, the warning makes it look
1153+
* like the developer did something wrong which is
1154+
* not true.
1155+
*/
1156+
if (hasValue) {
1157+
warnIfValueOutOfBounds(valueToProcess, minParts, maxParts);
1158+
}
11531159

11541160
/**
11551161
* If there are multiple values, pick an arbitrary one to clamp to. This way,

core/src/components/datetime/test/minmax/datetime.e2e.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,27 +111,34 @@ test.describe('datetime: minmax', () => {
111111
});
112112

113113
test.describe('setting value outside bounds should show in-bounds month', () => {
114-
const testDisplayedMonth = async (page: E2EPage, content: string) => {
114+
test.beforeEach(({ skip }) => {
115+
skip.rtl();
116+
});
117+
const testDisplayedMonth = async (page: E2EPage, content: string, expectedString = 'June 2021') => {
115118
await page.setContent(content);
116119
await page.waitForSelector('.datetime-ready');
117120

118121
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
119-
await expect(calendarMonthYear).toHaveText('June 2021');
122+
await expect(calendarMonthYear).toHaveText(expectedString);
120123
};
121124

122-
test('when min is defined', async ({ page }) => {
125+
test('when min and value are defined', async ({ page }) => {
123126
await testDisplayedMonth(page, `<ion-datetime min="2021-06-01" value="2021-05-01"></ion-datetime>`);
124127
});
125128

126-
test('when max is defined', async ({ page }) => {
129+
test('when max and value are defined', async ({ page }) => {
127130
await testDisplayedMonth(page, `<ion-datetime max="2021-06-30" value="2021-07-01"></ion-datetime>`);
128131
});
129132

130-
test('when both min and max are defined', async ({ page }) => {
133+
test('when min, max, and value are defined', async ({ page }) => {
131134
await testDisplayedMonth(
132135
page,
133136
`<ion-datetime min="2021-06-01" max="2021-06-30" value="2021-05-01"></ion-datetime>`
134137
);
135138
});
139+
140+
test('when max is defined', async ({ page }) => {
141+
await testDisplayedMonth(page, `<ion-datetime max="2012-06-01"></ion-datetime>`, 'June 2012');
142+
});
136143
});
137144
});

core/src/components/datetime/test/parse.spec.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { clampDate, getPartsFromCalendarDay, parseAmPm } from '../utils/parse';
1+
import { clampDate, getPartsFromCalendarDay, parseAmPm, parseMinParts, parseMaxParts } from '../utils/parse';
22

33
describe('getPartsFromCalendarDay()', () => {
44
it('should extract DatetimeParts from a calendar day element', () => {
@@ -72,3 +72,89 @@ describe('parseAmPm()', () => {
7272
expect(parseAmPm(11)).toEqual('am');
7373
});
7474
});
75+
76+
describe('parseMinParts()', () => {
77+
it('should fill in missing information when not provided', () => {
78+
const today = {
79+
day: 14,
80+
month: 3,
81+
year: 2022,
82+
minute: 4,
83+
hour: 2,
84+
};
85+
expect(parseMinParts('2012', today)).toEqual({
86+
month: 1,
87+
day: 1,
88+
year: 2012,
89+
hour: 0,
90+
minute: 0,
91+
});
92+
});
93+
it('should default to current year when only given HH:mm', () => {
94+
const today = {
95+
day: 14,
96+
month: 3,
97+
year: 2022,
98+
minute: 4,
99+
hour: 2,
100+
};
101+
expect(parseMinParts('04:30', today)).toEqual({
102+
month: 1,
103+
day: 1,
104+
year: 2022,
105+
hour: 4,
106+
minute: 30,
107+
});
108+
});
109+
});
110+
111+
describe('parseMaxParts()', () => {
112+
it('should fill in missing information when not provided', () => {
113+
const today = {
114+
day: 14,
115+
month: 3,
116+
year: 2022,
117+
minute: 4,
118+
hour: 2,
119+
};
120+
expect(parseMaxParts('2012', today)).toEqual({
121+
month: 12,
122+
day: 31,
123+
year: 2012,
124+
hour: 23,
125+
minute: 59,
126+
});
127+
});
128+
it('should default to current year when only given HH:mm', () => {
129+
const today = {
130+
day: 14,
131+
month: 3,
132+
year: 2022,
133+
minute: 4,
134+
hour: 2,
135+
};
136+
expect(parseMaxParts('04:30', today)).toEqual({
137+
month: 12,
138+
day: 31,
139+
year: 2022,
140+
hour: 4,
141+
minute: 30,
142+
});
143+
});
144+
it('should fill in correct day during a leap year', () => {
145+
const today = {
146+
day: 14,
147+
month: 3,
148+
year: 2022,
149+
minute: 4,
150+
hour: 2,
151+
};
152+
expect(parseMaxParts('2012-02', today)).toEqual({
153+
month: 2,
154+
day: 29,
155+
year: 2012,
156+
hour: 23,
157+
minute: 59,
158+
});
159+
});
160+
});

core/src/components/datetime/utils/parse.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { DatetimeParts } from '../datetime-interface';
22

33
import { isAfter, isBefore } from './comparison';
4+
import { getNumDaysInMonth } from './helpers';
45

56
const ISO_8601_REGEXP =
67
// eslint-disable-next-line no-useless-escape
@@ -138,3 +139,72 @@ export const clampDate = (
138139
export const parseAmPm = (hour: number) => {
139140
return hour >= 12 ? 'pm' : 'am';
140141
};
142+
143+
/**
144+
* Takes a max date string and creates a DatetimeParts
145+
* object, filling in any missing information.
146+
* For example, max="2012" would fill in the missing
147+
* month, day, hour, and minute information.
148+
*/
149+
export const parseMaxParts = (max: string, todayParts: DatetimeParts): DatetimeParts => {
150+
const { month, day, year, hour, minute } = parseDate(max);
151+
152+
/**
153+
* When passing in `max` or `min`, developers
154+
* can pass in any ISO-8601 string. This means
155+
* that not all of the date/time fields are defined.
156+
* For example, passing max="2012" is valid even though
157+
* there is no month, day, hour, or minute data.
158+
* However, all of this data is required when clamping the date
159+
* so that the correct initial value can be selected. As a result,
160+
* we need to fill in any omitted data with the min or max values.
161+
*/
162+
163+
const yearValue = year ?? todayParts.year;
164+
const monthValue = month ?? 12;
165+
return {
166+
month: monthValue,
167+
day: day ?? getNumDaysInMonth(monthValue, yearValue),
168+
/**
169+
* Passing in "HH:mm" is a valid ISO-8601
170+
* string, so we just default to the current year
171+
* in this case.
172+
*/
173+
year: yearValue,
174+
hour: hour ?? 23,
175+
minute: minute ?? 59,
176+
};
177+
};
178+
179+
/**
180+
* Takes a min date string and creates a DatetimeParts
181+
* object, filling in any missing information.
182+
* For example, min="2012" would fill in the missing
183+
* month, day, hour, and minute information.
184+
*/
185+
export const parseMinParts = (min: string, todayParts: DatetimeParts): DatetimeParts => {
186+
const { month, day, year, hour, minute } = parseDate(min);
187+
188+
/**
189+
* When passing in `max` or `min`, developers
190+
* can pass in any ISO-8601 string. This means
191+
* that not all of the date/time fields are defined.
192+
* For example, passing max="2012" is valid even though
193+
* there is no month, day, hour, or minute data.
194+
* However, all of this data is required when clamping the date
195+
* so that the correct initial value can be selected. As a result,
196+
* we need to fill in any omitted data with the min or max values.
197+
*/
198+
return {
199+
month: month ?? 1,
200+
day: day ?? 1,
201+
/**
202+
* Passing in "HH:mm" is a valid ISO-8601
203+
* string, so we just default to the current year
204+
* in this case.
205+
*/
206+
year: year ?? todayParts.year,
207+
hour: hour ?? 0,
208+
minute: minute ?? 0,
209+
};
210+
};

0 commit comments

Comments
 (0)