Skip to content

fix(datetime-button): fix initial text not obeying datetime constraints#31218

Open
droc101 wants to merge 5 commits into
ionic-team:mainfrom
droc101:main
Open

fix(datetime-button): fix initial text not obeying datetime constraints#31218
droc101 wants to merge 5 commits into
ionic-team:mainfrom
droc101:main

Conversation

@droc101

@droc101 droc101 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Issue number: resolves #30183


What is the current behavior?

an IonDatetimeButton's default value (such as when .value is undefined) always uses the exact current date & time, regardless of any restrictions requested by the associated IonDatetime (such as only allowing 5 minute increments)

What is the new behavior?

  • The default display value of an IonDatetimeButton now follows the constraints on its associated IonDatetime
  • The actual default .value of the IonDatetimeButton is unchanged
  • A new public method async getClosestDate(date: Date) => Promise<Date> has been added to IonDatetime to facilitate this change. If this should not be a public method, I can work on changing that.
  • A new private method getClosestDatetimeParts(parts: DatetimeParts) => DatetimeParts has been added to IonDatetime as a helper to the previously mentioned getClosestDate method, and to deduplicate code.

Does this introduce a breaking change?

  • Yes
  • No

…aints

expose new `getClosestDate(date: Date) => Promise<Date>` function in the Datetime class and use it in DatetimeButton to round the current time (default value) into a date that matches the constraints provided on the Datetime element (dayValues, minuteValues, etc.)

closes ionic-team#30183
@droc101 droc101 requested a review from a team as a code owner June 15, 2026 22:04
@droc101 droc101 requested a review from BenOsodrac June 15, 2026 22:04
@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

@droc101 is attempting to deploy a commit to the Ionic Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added package: core @ionic/core package package: angular @ionic/angular package labels Jun 15, 2026
@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ionic-framework Ready Ready Preview, Comment Jun 18, 2026 6:39pm

Request Review

@thetaPC thetaPC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add tests to prevent any regressions in the future. I would recommend adding them to datetime-button/test/basic/datetime-button.e2e.ts:

  • presentation="date" with no constraints — guards the plain day/month fallback (this is the case the current bug breaks).
  • presentation="time" with minute-values="0" — the originally reported case from #30183.
  • At least one other constraint, e.g. hour-values or month-values, to confirm it's not minute-specific.

Comment on lines +642 to +643
month: date.getMonth(),
day: date.getDay(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DatetimeParts and the native Date API don't use the same conventions, and two fields are mismatched here:

  1. month is off by one. Date.getMonth() is 0-indexed (January is 0), but DatetimeParts.month is 1-indexed (January is 1). So today, June, comes through as month 5, which DatetimeParts reads as May.

  2. day is the wrong field entirely. Date.getDay() returns the day of the week (0–6, Sun–Sat), not the day of the month. The day of the month comes from Date.getDate().

You can reproduce it with a plain no-value date picker (no constraints needed): the calendar lands on Jun 17, 2026 but the button reads "May 3, 2026".

Both of these can be seen with:

<ion-item>
  <ion-label>Start Date</ion-label>
  <ion-datetime-button slot="end" datetime="no-value-date"></ion-datetime-button>
</ion-item>

<!-- keep-contents-mounted makes the datetime (and the button text) compute on load, so the mismatch shows immediately -->
<ion-modal keep-contents-mounted="true">
  <ion-datetime locale="en-US" presentation="date" id="no-value-date"></ion-datetime>
</ion-modal>

Fix:

Suggested change
month: date.getMonth(),
day: date.getDay(),
month: date.getMonth() + 1,
day: date.getDate(),

Comment on lines +206 to +207
const defaultDatetime = [(await datetimeEl.getClosestDate(new Date())).toISOString()];
const parsedDatetimes = parseDate(parsedValues.length > 0 ? parsedValues : defaultDatetime);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getClosestDate is awaited on every call, but the result is thrown away whenever parsedValues is non-empty (i.e. whenever the datetime has a value). Since getClosestDate is a Stencil @Method, this is an async cross-component round-trip on every ionValueChange, just to discard the result. Move it into the no-value branch and also update the comment:

Suggested change
const defaultDatetime = [(await datetimeEl.getClosestDate(new Date())).toISOString()];
const parsedDatetimes = parseDate(parsedValues.length > 0 ? parsedValues : defaultDatetime);
/**
* Both ion-datetime and ion-datetime-button default to today's date
* and time if no value is set. The datetime is queried for the closest
* valid value so the button respects the same constraints (min, max,
* minuteValues, etc.) that the datetime applies to its own default.
*/
let valuesToParse = parsedValues;
if (valuesToParse.length === 0) {
const closestDate = await datetimeEl.getClosestDate(new Date());
valuesToParse = [closestDate.toISOString()];
}
const parsedDatetimes = parseDate(valuesToParse);

Comment on lines +635 to +650
/**
* Get the closest valid Date according to the restrictions on this Datetime
* @param date The Date to find the closest valid value for
*/
@Method()
async getClosestDate(date: Date) {
const closest = this.getClosestDatetimeParts({
month: date.getMonth(),
day: date.getDay(),
year: date.getFullYear(),
dayOfWeek: date.getDay(),
hour: date.getHours(),
minute: date.getMinutes(),
});
return removeDateTzOffset(new Date(convertDataToISO(closest)));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend an @internal method instead of a new public one.

componentWillLoad already computes this.defaultParts (today snapped to the closest valid value via getClosestValidDate, datetime.tsx:1540) — exactly what the button is trying to reconstruct. So the button can just read that value rather than recomputing it from a Date, which also guarantees the button and picker never disagree.

Making it @internal follows how these two already communicate (the button listens to the @internal ionValueChange event), and avoids adding public API to document and support. It also avoids reconstructing parts from a Date (the ISO -> removeDateTzOffset round-trip), which is where the getMonth()/getDay() mistakes lived.

Suggested change
/**
* Get the closest valid Date according to the restrictions on this Datetime
* @param date The Date to find the closest valid value for
*/
@Method()
async getClosestDate(date: Date) {
const closest = this.getClosestDatetimeParts({
month: date.getMonth(),
day: date.getDay(),
year: date.getFullYear(),
dayOfWeek: date.getDay(),
hour: date.getHours(),
minute: date.getMinutes(),
});
return removeDateTzOffset(new Date(convertDataToISO(closest)));
}
/**
* Returns the default parts the datetime falls back to when no value is set:
* today's date and time snapped to the closest value allowed by the
* component's constraints (`min`, `max`, and the `*Values` props).
*
* @internal
*/
@Method()
async getDefaultPart(): Promise<DatetimeParts> {
return this.defaultParts;
}

The datetime-button.tsx then reads it in the no-value branch

/**
     * Both ion-datetime and ion-datetime-button default to today's date and
     * time if no value is set. We read the datetime's computed default so the
     * button respects the same constraints (min, max, minuteValues, etc.) that
     * the datetime applies to its own fallback, instead of using a raw "now".
     */
    const parsedDatetimes =
      parsedValues.length > 0 ? parseDate(parsedValues) : [await datetimeEl.getDefaultPart()];

@github-actions github-actions Bot removed the package: angular @ionic/angular package label Jun 17, 2026
@droc101

droc101 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Tests will be coming in a separate future commit, have to figure out how that side of the code works.

@thetaPC

thetaPC commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

@droc101 I highly recommend reviewing the E2E testing doc.

- should default to exact current time with no constraints
- should obey minuteValues constraint
- should obey hourValues constraint
- should obey monthValues constraint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: core @ionic/core package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: ion-datetime-button does not respect minuteValues and others

2 participants