Skip to content

Date Picker

The Date Picker component provides a visual interface for picking a date or a date range. It is a dialog that provides multiple options for navigating and selecting dates, including a FioriCalendar, text input fields, year selection menu, and header and footer buttons.

Date Picker:

Date Picker

Date Input:

Date Picker Input

Date Range Picker:

Date Range Picker

Date Range Input:

Date Range Picker Input

Year Selector:

Year Selector

Using the Date Picker

Main API

The DatePicker composable function contains the dialog with date picking functionalities. The API is as follows:

@Composable
fun DatePicker(
    modifier: Modifier = Modifier,
    state: FioriDatePickerState = rememberFioriDatePickerState(),
    pickerOpened: Boolean,
    updatePickerOpened: (Boolean) -> Unit,
    updatePickedDateString: ((String) -> Unit)? = null,
    updateDateCleared: ((Boolean) -> Unit)? = null,
    supportText: String = stringResource(R.string.date_picker_support),
    rangeSupportText: String = stringResource(R.string.date_picker_range_support),
    pickerFormatter: String = "E, MMM d",
    rangeFormatter: String = "MMM d",
    useInput: Boolean = false,
    useRange: Boolean = false,
    onDateClicked: ((Date, FioriDatePickerState, FioriCalendarState) -> Unit)? = null,
    onDateCleared: ((FioriDatePickerState) -> Unit)? = null,
    onNegativeButtonClicked: ((FioriDatePickerState) -> Unit)? = null,
    onPositiveButtonClicked: ((FioriDatePickerState) -> Unit)? = null,
    onCancel: ((FioriDatePickerState) -> Unit)? = null,
    colors: DatePickerColors = DatePickerDefaults.colors(),
    textStyles: DatePickerTextStyles = DatePickerDefaults.textStyles(),
    styles: DatePickerStyles = DatePickerDefaults.styles()
)

This API creates a Date Picker with the default configuration, which is single date selection mode in calendar view with no date selected. The FioriDatePickerState class consists of a FioriCalendarData object that allows application developers to configure certain parts of the calendar view as per their own requirements. See Fiori Calendar for more details on how FioriCalendar can be configured.

class FioriDatePickerState(
    val data: FioriCalendarData = FioriCalendarData()
)

A function that remembers the FioriDatePickerState is also provided, which can be used when the parameters of the FioriCalendarData class are not expected to change after initialization.

@Composable
fun rememberFioriDatePickerState(
    data: FioriCalendarData = FioriCalendarData()
): FioriDatePickerState = rememberSaveable(saver = FioriDatePickerState.Saver()) {
    FioriDatePickerState(data)
}

Note that certain parameters of the FioriCalendarData object will not be customizable for the DatePicker component. The following parameters are not customizable:

FioriCalendarData(
    viewType = /** ViewType.SCROLL or ViewType.MONTH depending on the useRange parameter */,
    showOutOfMonthDates = false,
    showMonthViewInLandscapeMode = true,
    isSelectionPersistent = true,
    headerData = HeaderData(enabled = false)
)

API Parameters

Some of the parameters are explained in more detail below.

Picker Opened

The pickerOpened parameter determines whether the Date Picker dialog is opened or not.

Update Picker Opened

The updatePickerOpened parameter is a function that is called when the Date Picker changes its opened state. The Boolean that is returned should be used to update the pickerOpened parameter.

Update Picked Date String

The updatePickedDateString parameter is a function that is called when the user selects a date. The formatted selected date or date range is returned as a String.

Update Date Cleared

The updateDateCleared parameter is a function that is called when the date cleared state is changed, whether through clearing the date selection or successfully selecting a date. The Boolean that is returned represents whether the date selection is cleared or not.

Support Text

The Date Picker component contains text at the top of the dialog that should describe the purpose of the Date Picker when selecting a single date. This text can be customized using the supportText parameter.

Range Support Text

The Date Picker component contains text at the top of the dialog that should describe the purpose of the Date Picker when using range selection. This text can be customized using the rangeSupportText parameter.

Picker Formatter

The pickerFormatter parameter is a String that represents the format of the selected date shown as the header text when using the Date Picker. The default format is "E, MMM d".

Range Formatter

The rangeFormatter parameter is a String that represents the format of the selected start and end dates shown as the header text when using range selection. The default format is "MMM d".

Use Input

The Date Picker can be initialized with either the calendar view displayed or the text input fields displayed. The text input fields will be initially displayed if the useInput parameter is set to true.

Use Range

The Date Picker can be used for either single date selection or date range selection. Date range selection is enabled when the useRange parameter is set to true. When using date range selection, the FioriCalendar will be displayed in scroll view mode.

On Date Clicked

The onDateClicked parameter can be used to notify when a date has been clicked. It returns the clicked date as a Date object in the callback, along with the FioriDatePickerState and the FioriCalendarState used in the Date Picker. Note that this is only invoked when the application user taps on a date to select it.

On Date Cleared

The onDateCleared parameter can be used to notify when the date selection is cleared. It returns the FioriDatePickerState used in the Date Picker.

On Negative Button Clicked

The onNegativeButtonClicked parameter can be used to notify when the negative button is clicked. It returns the FioriDatePickerState used in the Date Picker.

On Positive Button Clicked

The onPositiveButtonClicked parameter can be used to notify when the positive button is clicked. It returns the FioriDatePickerState used in the Date Picker.

On Cancel

The onCancel parameter can be used to notify when the Date Picker is canceled through the upper icon button in the header when using range selection. It returns the FioriDatePickerState used in the Date Picker.

Code Samples

Default Date Picker

This code snippet demonstrates the simplest use of the Date Picker initialized with default values. It creates a Date Picker in calendar view using single date selection with no selected date.

var pickerOpened by rememberSaveable { mutableStateOf(false) }
val updatePickerOpened: (Boolean) -> Unit = {
    pickerOpened = it
}

DatePicker(
    pickerOpened = pickerOpened,
    updatePickerOpened = updatePickerOpened
)

Date Range Picker With Disabled Dates and Returned Range

This code snippet demonstrates how to create a Date Picker with disabled dates and how to retrieve the selected date range through the pickedRange property of the FioriDatePickerState object. Fridays are considered disabled and will not appear in the list of picked dates.

DatePicker(
    state = FioriDatePickerState(
        FioriCalendarData(
            isDateDisabled = {
                // getCalendar() implementation can be found in the Util.kt file for Fiori Calendar
                val currentCal = getCalendar(getCurrentLocale(), CalendarType.GREGORIAN)
                currentCal.time = it
                currentCal.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY
            }
        )
    ),
    useRange = true,
    onPositiveButtonClicked = { state ->
        val formatter = SimpleDateFormat("dd MMMM yyyy", getCurrentLocale())
        val pickedRangeString = state.pickedRange.value.joinToString("\n") { date -> formatter.format(date) }
        println("Picked dates: \n$pickedRangeString")
    }
)

Custom Range Selection Behavior (Cannot Cross Disabled Dates)

This code snippet demonstrates how to create a Date Picker with customized behavior for range selection. The Date Picker will not allow the user to select a date range that crosses disabled dates by setting the selectable upper bound date to the next disabled date after the selected start date. Successfully selecting an end date, clearing the date selection, or closing the Date Picker will reset the end date to the default value, allowing any non-disabled date to be selected again.

// getDefaultStartEndDate() implementation can be found in the Util.kt file for Fiori Calendar
var endDate by rememberSaveable { mutableStateOf(getDefaultStartEndDate(getCurrentLocale(), false)) }
val disabledDates = ArrayList<Date>()
val cal = getCalendar(getCurrentLocale())
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 3)
disabledDates.add(cal.time)
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 10)
disabledDates.add(cal.time)
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 15)
disabledDates.add(cal.time)
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 21)
disabledDates.add(cal.time)
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 27)
disabledDates.add(cal.time)

val isDateDisabled: (Date) -> Boolean = {
    var isDisabled = false
    // getCalendar() implementation can be found in the Util.kt file for Fiori Calendar
    val currentCal = getCalendar(getCurrentLocale(), CalendarType.GREGORIAN)
    currentCal.time = it
    val disabledCal = getCalendar(getCurrentLocale(), CalendarType.GREGORIAN)
    for (date in disabledDates) {
        disabledCal.time = date
        // isSameDate() implementation can be found in the Util.kt file for Fiori Calendar
        if (isSameDate(currentCal, disabledCal)) {
            isDisabled = true
        }
    }
    isDisabled || it > endDate
}

DatePicker(
    state = FioriDatePickerState(
        FioriCalendarData(
            useRangeSelection = true,
            isDateDisabled = isDateDisabled
        )
    ),
    rangeSupportText = "Select range without crossing disabled dates",
    useRange = true,
    onDateClicked = { date, state, _ ->
        val nextDisabledDateIndex = findNextDisabledDateIndex(date, disabledDates)
        endDate = if (state.rangePickOn.value) {
            getDefaultStartEndDate(getCurrentLocale(), false)
        } else {
            if (nextDisabledDateIndex >= 0) {
                disabledDates[nextDisabledDateIndex]
            } else {
                getDefaultStartEndDate(getCurrentLocale(), false)
            }
        }
    },
    onDateCleared = { state ->
        endDate = getDefaultStartEndDate(state.data.locale, false)
    },
    onNegativeButtonClicked = { state ->
        endDate = getDefaultStartEndDate(state.data.locale, false)
    },
    onPositiveButtonClicked = { state ->
        endDate = getDefaultStartEndDate(state.data.locale, false)
    },
    onCancel = { state ->
        endDate = getDefaultStartEndDate(state.data.locale, false)
    }
)

/**
 * An example of how to find the next disabled date after a selected date given an ordered list of
 * disabled dates. This can be used to customize range selection behavior depending on the selected
 * date.
 */
fun findNextDisabledDateIndex(date: Date, disabledDates: List<Date>): Int {
    if (disabledDates.isNotEmpty()) {
        val cal = getCalendar(getCurrentLocale(), CalendarType.GREGORIAN)
        cal.time = date
        var low = 0
        var mid = 0
        var high = disabledDates.size - 1
        while (low <= high) {
            mid = (low + high) / 2
            if (cal.time.after(disabledDates[mid])) {
                low = mid + 1
            } else if (cal.time.before(disabledDates[mid])) {
                high = mid - 1
            } else {
                return mid
            }
        }
        return if (cal.time.before(disabledDates[mid])) {
            mid
        } else {
            if (mid + 1 < disabledDates.size) {
                mid + 1
            } else {
                -1
            }
        }
    } else {
        return -1
    }
}

Custom Range Selection Behavior (Reset Range If Disabled Date Is Crossed)

This code snippet demonstrates how to create a Date Picker with customized behavior for range selection. The Date Picker will reset the selected date range if the user tries to select a date range that crosses a disabled date. The end date that is past the disabled date will be considered the start date of a new range selection. The code utilizes the selectedRangeStartDate and selectedRangeEndDate properties of the FioriDatePickerState object to achieve this behavior.

// getDefaultStartEndDate() implementation can be found in the Util.kt file for Fiori Calendar
var endDate by rememberSaveable { mutableStateOf(getDefaultStartEndDate(getCurrentLocale(), false)) }
val disabledDates = ArrayList<Date>()
val cal = getCalendar(getCurrentLocale())
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 3)
disabledDates.add(cal.time)
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 10)
disabledDates.add(cal.time)
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 15)
disabledDates.add(cal.time)
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 21)
disabledDates.add(cal.time)
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 27)
disabledDates.add(cal.time)

val isDateDisabled: (Date) -> Boolean = {
    var isDisabled = false
    // getCalendar() implementation can be found in the Util.kt file for Fiori Calendar
    val currentCal = getCalendar(getCurrentLocale(), CalendarType.GREGORIAN)
    currentCal.time = it
    val disabledCal = getCalendar(getCurrentLocale(), CalendarType.GREGORIAN)
    for (date in disabledDates) {
        disabledCal.time = date
        // isSameDate() implementation can be found in the Util.kt file for Fiori Calendar
        if (isSameDate(currentCal, disabledCal)) {
            isDisabled = true
        }
    }
    isDisabled || it > endDate
}

DatePicker(
    state = FioriDatePickerState(
        FioriCalendarData(
            endDate = endDate,
            useRangeSelection = true,
            isDateDisabled = isDateDisabled
        )
    ),
    rangeSupportText = "Range will reset if it crosses a disabled date",
    useRange = true,
    onDateClicked = { date, state, calendarState ->
        if (state.rangePickOn.value) {
            val currCal = getCalendar(calendarState.locale)
            currCal.time = state.selectedRangeStartDate.value
            while (currCal.time.before(state.selectedRangeEndDate.value)) {
                if (isDateDisabled(currCal.time)) {
                    fioriCalendarUpdateSelectedDate(calendarState, date)
                    state.selectedRangeStartDate.value = date
                    state.selectedRangeEndDate.value = date
                    break
                }
                currCal.add(Calendar.DATE, 1)
            }
        }
    }
)

Last update: May 2, 2025