diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx
index a9250cfa32..8db5b2fe20 100644
--- a/core/src/components/datetime/datetime.tsx
+++ b/core/src/components/datetime/datetime.tsx
@@ -46,7 +46,15 @@ import {
getPreviousYear,
getStartOfWeek,
} from './utils/manipulation';
-import { clampDate, convertToArrayOfNumbers, getPartsFromCalendarDay, parseAmPm, parseDate } from './utils/parse';
+import {
+ clampDate,
+ convertToArrayOfNumbers,
+ getPartsFromCalendarDay,
+ parseAmPm,
+ parseDate,
+ parseMaxParts,
+ parseMinParts,
+} from './utils/parse';
import {
getCalendarDayState,
isDayDisabled,
@@ -774,37 +782,24 @@ export class Datetime implements ComponentInterface {
};
private processMinParts = () => {
- if (this.min === undefined) {
+ const { min, todayParts } = this;
+ if (min === undefined) {
this.minParts = undefined;
return;
}
- const { month, day, year, hour, minute } = parseDate(this.min);
-
- this.minParts = {
- month,
- day,
- year,
- hour,
- minute,
- };
+ this.minParts = parseMinParts(min, todayParts);
};
private processMaxParts = () => {
- if (this.max === undefined) {
+ const { max, todayParts } = this;
+
+ if (max === undefined) {
this.maxParts = undefined;
return;
}
- const { month, day, year, hour, minute } = parseDate(this.max);
-
- this.maxParts = {
- month,
- day,
- year,
- hour,
- minute,
- };
+ this.maxParts = parseMaxParts(max, todayParts);
};
private initializeCalendarListener = () => {
@@ -1140,7 +1135,8 @@ export class Datetime implements ComponentInterface {
}
private processValue = (value?: string | string[] | null) => {
- this.highlightActiveParts = !!value;
+ const hasValue = !!value;
+ this.highlightActiveParts = hasValue;
let valueToProcess = parseDate(value || getToday());
const { minParts, maxParts, multiple } = this;
@@ -1149,7 +1145,17 @@ export class Datetime implements ComponentInterface {
valueToProcess = (valueToProcess as DatetimeParts[])[0];
}
- warnIfValueOutOfBounds(valueToProcess, minParts, maxParts);
+ /**
+ * Datetime should only warn of out of bounds values
+ * if set by the user. If the `value` is undefined,
+ * we will default to today's date which may be out
+ * of bounds. In this case, the warning makes it look
+ * like the developer did something wrong which is
+ * not true.
+ */
+ if (hasValue) {
+ warnIfValueOutOfBounds(valueToProcess, minParts, maxParts);
+ }
/**
* If there are multiple values, pick an arbitrary one to clamp to. This way,
diff --git a/core/src/components/datetime/test/minmax/datetime.e2e.ts b/core/src/components/datetime/test/minmax/datetime.e2e.ts
index 4a97594361..f3e434d6ed 100644
--- a/core/src/components/datetime/test/minmax/datetime.e2e.ts
+++ b/core/src/components/datetime/test/minmax/datetime.e2e.ts
@@ -111,27 +111,34 @@ test.describe('datetime: minmax', () => {
});
test.describe('setting value outside bounds should show in-bounds month', () => {
- const testDisplayedMonth = async (page: E2EPage, content: string) => {
+ test.beforeEach(({ skip }) => {
+ skip.rtl();
+ });
+ const testDisplayedMonth = async (page: E2EPage, content: string, expectedString = 'June 2021') => {
await page.setContent(content);
await page.waitForSelector('.datetime-ready');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
- await expect(calendarMonthYear).toHaveText('June 2021');
+ await expect(calendarMonthYear).toHaveText(expectedString);
};
- test('when min is defined', async ({ page }) => {
+ test('when min and value are defined', async ({ page }) => {
await testDisplayedMonth(page, ``);
});
- test('when max is defined', async ({ page }) => {
+ test('when max and value are defined', async ({ page }) => {
await testDisplayedMonth(page, ``);
});
- test('when both min and max are defined', async ({ page }) => {
+ test('when min, max, and value are defined', async ({ page }) => {
await testDisplayedMonth(
page,
``
);
});
+
+ test('when max is defined', async ({ page }) => {
+ await testDisplayedMonth(page, ``, 'June 2012');
+ });
});
});
diff --git a/core/src/components/datetime/test/parse.spec.ts b/core/src/components/datetime/test/parse.spec.ts
index 641b075349..7e152a3c95 100644
--- a/core/src/components/datetime/test/parse.spec.ts
+++ b/core/src/components/datetime/test/parse.spec.ts
@@ -1,4 +1,4 @@
-import { clampDate, getPartsFromCalendarDay, parseAmPm } from '../utils/parse';
+import { clampDate, getPartsFromCalendarDay, parseAmPm, parseMinParts, parseMaxParts } from '../utils/parse';
describe('getPartsFromCalendarDay()', () => {
it('should extract DatetimeParts from a calendar day element', () => {
@@ -72,3 +72,89 @@ describe('parseAmPm()', () => {
expect(parseAmPm(11)).toEqual('am');
});
});
+
+describe('parseMinParts()', () => {
+ it('should fill in missing information when not provided', () => {
+ const today = {
+ day: 14,
+ month: 3,
+ year: 2022,
+ minute: 4,
+ hour: 2,
+ };
+ expect(parseMinParts('2012', today)).toEqual({
+ month: 1,
+ day: 1,
+ year: 2012,
+ hour: 0,
+ minute: 0,
+ });
+ });
+ it('should default to current year when only given HH:mm', () => {
+ const today = {
+ day: 14,
+ month: 3,
+ year: 2022,
+ minute: 4,
+ hour: 2,
+ };
+ expect(parseMinParts('04:30', today)).toEqual({
+ month: 1,
+ day: 1,
+ year: 2022,
+ hour: 4,
+ minute: 30,
+ });
+ });
+});
+
+describe('parseMaxParts()', () => {
+ it('should fill in missing information when not provided', () => {
+ const today = {
+ day: 14,
+ month: 3,
+ year: 2022,
+ minute: 4,
+ hour: 2,
+ };
+ expect(parseMaxParts('2012', today)).toEqual({
+ month: 12,
+ day: 31,
+ year: 2012,
+ hour: 23,
+ minute: 59,
+ });
+ });
+ it('should default to current year when only given HH:mm', () => {
+ const today = {
+ day: 14,
+ month: 3,
+ year: 2022,
+ minute: 4,
+ hour: 2,
+ };
+ expect(parseMaxParts('04:30', today)).toEqual({
+ month: 12,
+ day: 31,
+ year: 2022,
+ hour: 4,
+ minute: 30,
+ });
+ });
+ it('should fill in correct day during a leap year', () => {
+ const today = {
+ day: 14,
+ month: 3,
+ year: 2022,
+ minute: 4,
+ hour: 2,
+ };
+ expect(parseMaxParts('2012-02', today)).toEqual({
+ month: 2,
+ day: 29,
+ year: 2012,
+ hour: 23,
+ minute: 59,
+ });
+ });
+});
diff --git a/core/src/components/datetime/utils/parse.ts b/core/src/components/datetime/utils/parse.ts
index 4eb54ac7d2..ebfe2a94ea 100644
--- a/core/src/components/datetime/utils/parse.ts
+++ b/core/src/components/datetime/utils/parse.ts
@@ -1,6 +1,7 @@
import type { DatetimeParts } from '../datetime-interface';
import { isAfter, isBefore } from './comparison';
+import { getNumDaysInMonth } from './helpers';
const ISO_8601_REGEXP =
// eslint-disable-next-line no-useless-escape
@@ -138,3 +139,72 @@ export const clampDate = (
export const parseAmPm = (hour: number) => {
return hour >= 12 ? 'pm' : 'am';
};
+
+/**
+ * Takes a max date string and creates a DatetimeParts
+ * object, filling in any missing information.
+ * For example, max="2012" would fill in the missing
+ * month, day, hour, and minute information.
+ */
+export const parseMaxParts = (max: string, todayParts: DatetimeParts): DatetimeParts => {
+ const { month, day, year, hour, minute } = parseDate(max);
+
+ /**
+ * When passing in `max` or `min`, developers
+ * can pass in any ISO-8601 string. This means
+ * that not all of the date/time fields are defined.
+ * For example, passing max="2012" is valid even though
+ * there is no month, day, hour, or minute data.
+ * However, all of this data is required when clamping the date
+ * so that the correct initial value can be selected. As a result,
+ * we need to fill in any omitted data with the min or max values.
+ */
+
+ const yearValue = year ?? todayParts.year;
+ const monthValue = month ?? 12;
+ return {
+ month: monthValue,
+ day: day ?? getNumDaysInMonth(monthValue, yearValue),
+ /**
+ * Passing in "HH:mm" is a valid ISO-8601
+ * string, so we just default to the current year
+ * in this case.
+ */
+ year: yearValue,
+ hour: hour ?? 23,
+ minute: minute ?? 59,
+ };
+};
+
+/**
+ * Takes a min date string and creates a DatetimeParts
+ * object, filling in any missing information.
+ * For example, min="2012" would fill in the missing
+ * month, day, hour, and minute information.
+ */
+export const parseMinParts = (min: string, todayParts: DatetimeParts): DatetimeParts => {
+ const { month, day, year, hour, minute } = parseDate(min);
+
+ /**
+ * When passing in `max` or `min`, developers
+ * can pass in any ISO-8601 string. This means
+ * that not all of the date/time fields are defined.
+ * For example, passing max="2012" is valid even though
+ * there is no month, day, hour, or minute data.
+ * However, all of this data is required when clamping the date
+ * so that the correct initial value can be selected. As a result,
+ * we need to fill in any omitted data with the min or max values.
+ */
+ return {
+ month: month ?? 1,
+ day: day ?? 1,
+ /**
+ * Passing in "HH:mm" is a valid ISO-8601
+ * string, so we just default to the current year
+ * in this case.
+ */
+ year: year ?? todayParts.year,
+ hour: hour ?? 0,
+ minute: minute ?? 0,
+ };
+};