diff --git a/core/api.txt b/core/api.txt index 8a9af0b2918..0ea7bdb53d4 100644 --- a/core/api.txt +++ b/core/api.txt @@ -545,6 +545,7 @@ ion-datetime,prop,value,null | string | string[] | undefined,undefined,false,fal ion-datetime,prop,yearValues,number | number[] | string | undefined,undefined,false,false ion-datetime,method,cancel,cancel(closeOverlay?: boolean) => Promise ion-datetime,method,confirm,confirm(closeOverlay?: boolean) => Promise +ion-datetime,method,getClosestDate,getClosestDate(date: Date) => Promise ion-datetime,method,reset,reset(startDate?: string) => Promise ion-datetime,event,ionBlur,void,true ion-datetime,event,ionCancel,void,true diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 443c04611cf..7710da9f8d3 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -961,6 +961,11 @@ export namespace Components { * Formatting options for dates and times. Should include a 'date' and/or 'time' object, each of which is of type [Intl.DateTimeFormatOptions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options). */ "formatOptions"?: FormatOptions; + /** + * Get the closest valid Date according to the restrictions on this Datetime + * @param date The Date to find the closest valid value for + */ + "getClosestDate": (date: Date) => Promise; /** * Used to apply custom text and background colors to specific dates. Can be either an array of objects containing ISO strings and colors, or a callback that receives an ISO string and returns the colors. Only applies to the `date`, `date-time`, and `time-date` presentations, with `preferWheel="false"`. */ diff --git a/core/src/components/datetime-button/datetime-button.tsx b/core/src/components/datetime-button/datetime-button.tsx index af0a77f88e0..965341116e4 100644 --- a/core/src/components/datetime-button/datetime-button.tsx +++ b/core/src/components/datetime-button/datetime-button.tsx @@ -7,7 +7,6 @@ import { createColorClasses } from '@utils/theme'; import { getIonMode } from '../../global/ionic-global'; import type { Color } from '../../interface'; import type { DatetimePresentation } from '../datetime/datetime-interface'; -import { getToday } from '../datetime/utils/data'; import { getLocalizedDateTime, getLocalizedTime } from '../datetime/utils/format'; import { getHourCycle } from '../datetime/utils/helpers'; import { parseDate } from '../datetime/utils/parse'; @@ -125,7 +124,7 @@ export class DatetimeButton implements ComponentInterface { overlayEl.classList.add('ion-datetime-button-overlay'); } - componentOnReady(datetimeEl, () => { + componentOnReady(datetimeEl, async () => { const datetimePresentation = (this.datetimePresentation = datetimeEl.presentation || 'date-time'); /** @@ -138,7 +137,7 @@ export class DatetimeButton implements ComponentInterface { * to re-render the displayed * text in the buttons. */ - this.setDateTimeText(); + await this.setDateTimeText(); addEventListener(datetimeEl, 'ionValueChange', this.setDateTimeText); /** @@ -189,7 +188,7 @@ export class DatetimeButton implements ComponentInterface { * ion-datetime and then format it according * to the locale specified on ion-datetime. */ - private setDateTimeText = () => { + private setDateTimeText = async () => { const { datetimeEl, datetimePresentation } = this; if (!datetimeEl) { @@ -204,7 +203,8 @@ export class DatetimeButton implements ComponentInterface { * Both ion-datetime and ion-datetime-button default * to today's date and time if no value is set. */ - const parsedDatetimes = parseDate(parsedValues.length > 0 ? parsedValues : [getToday()]); + const defaultDatetime = [(await datetimeEl.getClosestDate(new Date())).toISOString()]; + const parsedDatetimes = parseDate(parsedValues.length > 0 ? parsedValues : defaultDatetime); if (!parsedDatetimes) { return; diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index f36cd66718d..d5621e20677 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -35,7 +35,13 @@ import { getTimeColumnsData, getCombinedDateColumnData, } from './utils/data'; -import { formatValue, getLocalizedDateTime, getLocalizedTime, getMonthAndYear } from './utils/format'; +import { + formatValue, + getLocalizedDateTime, + getLocalizedTime, + getMonthAndYear, + removeDateTzOffset, +} from './utils/format'; import { isLocaleDayPeriodRTL, isMonthFirstLocale, getNumDaysInMonth, getHourCycle } from './utils/helpers'; import { calculateHourFromAMPM, @@ -604,6 +610,45 @@ export class Datetime implements ComponentInterface { } } + /** + * Get the closest valid DatetimeParts according to the restrictions on this Datetime + * @param parts The DatetimeParts to find the closest valid value for + */ + private getClosestDatetimeParts(parts: DatetimeParts) { + const hourValues = (this.parsedHourValues = convertToArrayOfNumbers(this.hourValues)); + const minuteValues = (this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues)); + const monthValues = (this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues)); + const yearValues = (this.parsedYearValues = convertToArrayOfNumbers(this.yearValues)); + const dayValues = (this.parsedDayValues = convertToArrayOfNumbers(this.dayValues)); + return getClosestValidDate({ + refParts: parts, + monthValues, + dayValues, + yearValues, + hourValues, + minuteValues, + minParts: this.minParts, + maxParts: this.maxParts, + }); + } + + /** + * 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))); + } + private warnIfIncorrectValueUsage = () => { const { multiple, value } = this; if (!multiple && Array.isArray(value)) { @@ -1495,27 +1540,12 @@ export class Datetime implements ComponentInterface { warnIfTimeZoneProvided(el, formatOptions); } - const hourValues = (this.parsedHourValues = convertToArrayOfNumbers(this.hourValues)); - const minuteValues = (this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues)); - const monthValues = (this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues)); - const yearValues = (this.parsedYearValues = convertToArrayOfNumbers(this.yearValues)); - const dayValues = (this.parsedDayValues = convertToArrayOfNumbers(this.dayValues)); - const todayParts = (this.todayParts = parseDate(getToday())!); this.processMinParts(); this.processMaxParts(); - this.defaultParts = getClosestValidDate({ - refParts: todayParts, - monthValues, - dayValues, - yearValues, - hourValues, - minuteValues, - minParts: this.minParts, - maxParts: this.maxParts, - }); + this.defaultParts = this.getClosestDatetimeParts(todayParts); this.processValue(this.value); diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index 1c78b120d9d..d6b63a168ec 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -635,7 +635,7 @@ Set `scrollEvents` to `true` to enable. @ProxyCmp({ inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'formatOptions', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showAdjacentDays', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues'], - methods: ['confirm', 'reset', 'cancel'] + methods: ['confirm', 'reset', 'cancel', 'getClosestDate'] }) @Component({ selector: 'ion-datetime',