# Range Calendar

Range calendars display a grid of days in one or more months and allow users to select a contiguous range of dates.

## Example

```python
from deephaven import ui

my_range_calendar_basic = ui.range_calendar(aria_label="Trip Dates")
```

## Date types

The range is a dictionary with a `start` date and an `end` date; e.g., `{ "start": "2024-01-02", "end": "2024-01-05" }`.

The range calendar accepts the following date types as inputs:

- `None`
- `LocalDate`
- `ZoneDateTime`
- `Instant`
- `int`
- `str`
- `datetime.datetime`
- `numpy.datetime64`
- `pandas.Timestamp`

The input will be converted to one of three Java date types:

1. `LocalDate`: A LocalDate is a date without a time zone in the ISO-8601 system, such as “2007-12-03” or “2057-01-28”.
2. `Instant`: An Instant represents an unambiguous specific point on the timeline, such as 2021-04-12T14:13:07 UTC.
3. `ZonedDateTime`: A ZonedDateTime represents an unambiguous specific point on the timeline with an associated time zone, such as 2021-04-12T14:13:07 America/New_York.

The format of the range calendar and the type of the value passed to the `on_change` handler is determined by the type of the following props in order of precedence:

1. `value`
2. `default_value`
3. `focused_value`
4. `default_focused_value`

If none of these are provided, the `on_change` handler passes a range of `Instant`.

```python
from deephaven import ui
from deephaven.time import to_j_local_date, to_j_instant, to_j_zdt


@ui.component
def range_calendar_example(start, end):
    dates, set_dates = ui.use_state({"start": start, "end": end})
    return [
        ui.range_calendar(on_change=set_dates, value=dates),
        ui.text(str(dates["start"])),
        ui.text(str(dates["end"])),
    ]


zdt_start = to_j_zdt("1995-03-22T11:11:11.23142 America/New_York")
zdt_end = to_j_zdt("1995-03-25T11:11:11.23142 America/New_York")
instant_start = to_j_instant("2022-01-01T00:00:00 ET")
instant_end = to_j_instant("2022-01-05T00:00:00 ET")
local_start = to_j_local_date("2024-05-06")
local_end = to_j_local_date("2024-05-10")

my_zoned_example = range_calendar_example(zdt_start, zdt_end)
my_instant_example = range_calendar_example(instant_start, instant_end)
my_local_example = range_calendar_example(local_start, local_end)
```

## Value

A range calendar has no selection by default. An initial, uncontrolled value can be provided to the range calendar using the `default_value` prop. Alternatively, a controlled value can be provided using the `value` prop.

```python
from deephaven import ui


@ui.component
def example():
    value, set_value = ui.use_state({"start": "2020-02-03", "end": "2020-02-08"})
    return ui.flex(
        ui.range_calendar(
            aria_label="Date range (uncontrolled)",
            default_value={"start": "2020-02-03", "end": "2020-02-08"},
        ),
        ui.range_calendar(
            aria_label="Date range (controlled)", value=value, on_change=set_value
        ),
        gap="size-300",
        wrap=True,
    )


my_example = example()
```

## Labeling

An `aria_label` must be provided to the Calendar for accessibility. If it is labeled by a separate element, an `aria_labelledby` prop must be provided using the id of the labeling element instead.

## Events

Range calendar accepts an `on_change` prop which is triggered whenever a date is selected by the user.

```python
from deephaven import ui


@ui.component
def event_example():
    value, set_value = ui.use_state({"start": "2020-02-03", "end": "2020-02-08"})
    return ui.range_calendar(
        aria_label="Calendar (controlled)", value=value, on_change=set_value
    )


my_event_example = event_example()
```

## Validation

By default, range calendar allows selecting any date range. The `min_value` and `max_value` props can also be used to prevent the user from selecting dates outside a certain range.

This example only accepts dates after today.

```python
from deephaven import ui
from deephaven.time import dh_today


my_range_calendar_min_value_example = ui.range_calendar(
    aria_label="Appointment Date", min_value=dh_today()
)
```

## Controlling the focused date

By default, the selected date is focused when a Calendar first mounts. If no `value` or `default_value` prop is provided, then the current date is focused. However, range calendar supports controlling which date is focused using the `focused_value` and `on_focus_change` props. This also determines which month is visible. The `default_focused_value` prop allows setting the initial focused date when the range calendar first mounts, without controlling it.

This example focuses July 1, 2021 by default. The user may change the focused date, and the `on_focus_change` event updates the state. Clicking the button resets the focused date back to the initial value.

```python
from deephaven import ui
from deephaven.time import to_j_local_date

default_date = to_j_local_date("2021-07-01")


@ui.component
def focused_example():
    value, set_value = ui.use_state(default_date)
    return ui.flex(
        ui.action_button(
            "Reset focused date", on_press=lambda: set_value(default_date)
        ),
        ui.range_calendar(focused_value=value, on_focus_change=set_value),
        direction="column",
        align_items="start",
        gap="size-200",
    )


my_focused_example = focused_example()
```

## Disabled state

The `is_disabled` prop disables the range calendar to prevent user interaction. This is useful when the range calendar should be visible but not available for selection.

```python
from deephaven import ui


my_range_calendar_is_disabled_example = ui.range_calendar(
    is_disabled=True,
)
```

## Read only

The `is_read_only` prop makes the range calendar’s value immutable. Unlike `is_disabled`, the range calendar remains focusable.

```python
from deephaven import ui


my_range_calendar_is_read_only_example = ui.range_calendar(
    is_read_only=True,
)
```

## Visible Months

By default, the range calendar displays a single month. The `visible_months` prop allows displaying up to 3 months at a time.

```python
from deephaven import ui


my_range_calendar_visible_months_example = ui.range_calendar(
    visible_months=3,
)
```

## Page Behavior

By default, when pressing the next or previous buttons, pagination will advance by the `visible_months` value. This behavior can be changed to page by single months instead, by setting `page_behavior` to `single`.

```python
from deephaven import ui


my_range_calendar_page_behavior_example = ui.range_calendar(
    visible_months=3, page_behavior="single"
)
```

## Time table filtering

Calendars can be used to filter tables with time columns.

```python
from deephaven.time import dh_now
from deephaven import time_table, ui


@ui.component
def date_table_filter(table, start_date, end_date, time_col="Timestamp"):
    dates, set_dates = ui.use_state({"start": start_date, "end": end_date})
    start = dates["start"]
    end = dates["end"]
    return [
        ui.range_calendar(value=dates, on_change=set_dates),
        table.where(f"{time_col} >= start && {time_col} < end"),
    ]


SECONDS_IN_DAY = 86400
today = dh_now()
_table = time_table("PT1s").update_view(
    ["Timestamp=today.plusSeconds(SECONDS_IN_DAY*i)", "Row=i"]
)
date_filter = date_table_filter(_table, today, today.plusSeconds(SECONDS_IN_DAY * 10))
```

## API Reference

A range_calendar allows the user to select a range of dates.

**Returns:** `Element` The range_calendar element.

<ParamTable param={{"module_name": "deephaven.ui.", "name": "range_calendar", "parameters": [{"name": "value", "type": "DateRange | None", "description": "The current value (controlled).", "default": "None"}, {"name": "default_value", "type": "DateRange | None", "description": "The default value (uncontrolled).", "default": "None"}, {"name": "focused_value", "type": "DType | None | str | date | datetime | datetime64 | Timestamp | int", "description": "Controls the currently focused date within the range_calendar.", "default": "None"}, {"name": "default_focused_value", "type": "DType | None | str | date | datetime | datetime64 | Timestamp | int", "description": "The date that is focused when the range_calendar first mounts (uncountrolled).", "default": "None"}, {"name": "min_value", "type": "DType | None | str | date | datetime | datetime64 | Timestamp | int", "description": "The minimum allowed date that a user may select.", "default": "None"}, {"name": "max_value", "type": "DType | None | str | date | datetime | datetime64 | Timestamp | int", "description": "The maximum allowed date that a user may select.", "default": "None"}, {"name": "page_behavior", "type": "Literal['single', 'visible'] | None", "description": "Controls the behavior of paging. Pagination either works by advancing the visible page by visibleDuration (default) or one unit of visibleDuration.", "default": "None"}, {"name": "is_invalid", "type": "bool | None", "description": "Whether the current selection is invalid according to application logic.", "default": "None"}, {"name": "is_disabled", "type": "bool | None", "description": "Whether the input is disabled.", "default": "None"}, {"name": "is_read_only", "type": "bool | None", "description": "Whether the input can be selected but not changed by the user.", "default": "None"}, {"name": "auto_focus", "type": "bool | None", "description": "Whether the element should receive focus on render.", "default": "None"}, {"name": "error_message", "type": "Element | None", "description": "An error message for the field.", "default": "None"}, {"name": "visible_months", "type": "int | None", "description": "The number of months to display at once. Up to 3 months are supported.", "default": "None"}, {"name": "on_focus_change", "type": "Callable[[None | DType | str | date | datetime | datetime64 | Timestamp], None] | None", "description": "Function called when the focus state changes.", "default": "None"}, {"name": "on_change", "type": "Callable[[DateRange], None] | None", "description": "Handler that is called when the value changes. The exact Date type will be the same as the type passed to value, default_value, focused_value, or default_focused_value in that order of precedence.", "default": "None"}, {"name": "flex", "type": "str | float | bool | None", "description": "When used in a flex layout, specifies how the element will grow or shrink to fit the space available.", "default": "None"}, {"name": "flex_grow", "type": "float | None", "description": "When used in a flex layout, specifies how much the element will grow to fit the space available.", "default": "None"}, {"name": "flex_shrink", "type": "float | None", "description": "When used in a flex layout, specifies how much the element will shrink to fit the space available.", "default": "None"}, {"name": "flex_basis", "type": "str | float | None", "description": "When used in a flex layout, specifies the initial size of the element.", "default": "None"}, {"name": "align_self", "type": "Literal['auto', 'normal', 'start', 'end', 'center', 'flex-start', 'flex-end', 'self-start', 'self-end', 'stretch'] | None", "description": "Overrides the align_items property of a flex or grid container.", "default": "None"}, {"name": "justify_self", "type": "Literal['auto', 'normal', 'start', 'end', 'flex-start', 'flex-end', 'self-start', 'self-end', 'center', 'left', 'right', 'stretch'] | None", "description": "Specifies how the element is justified inside a flex or grid container.", "default": "None"}, {"name": "order", "type": "int | None", "description": "The layout order for the element within a flex or grid container.", "default": "None"}, {"name": "grid_area", "type": "str | None", "description": "The name of the grid area to place the element in.", "default": "None"}, {"name": "grid_row", "type": "str | None", "description": "The name of the grid row to place the element in.", "default": "None"}, {"name": "grid_row_start", "type": "str | None", "description": "The name of the grid row to start the element in.", "default": "None"}, {"name": "grid_row_end", "type": "str | None", "description": "The name of the grid row to end the element in.", "default": "None"}, {"name": "grid_column", "type": "str | None", "description": "The name of the grid column to place the element in.", "default": "None"}, {"name": "grid_column_start", "type": "str | None", "description": "The name of the grid column to start the element in.", "default": "None"}, {"name": "grid_column_end", "type": "str | None", "description": "The name of the grid column to end the element in.", "default": "None"}, {"name": "margin", "type": "str | float | None", "description": "The margin to apply around the element.", "default": "None"}, {"name": "margin_top", "type": "str | float | None", "description": "The margin to apply above the element.", "default": "None"}, {"name": "margin_bottom", "type": "str | float | None", "description": "The margin to apply below the element.", "default": "None"}, {"name": "margin_start", "type": "str | float | None", "description": "The margin to apply before the element.", "default": "None"}, {"name": "margin_end", "type": "str | float | None", "description": "The margin to apply after the element.", "default": "None"}, {"name": "margin_x", "type": "str | float | None", "description": "The margin to apply to the left and right of the element.", "default": "None"}, {"name": "margin_y", "type": "str | float | None", "description": "The margin to apply to the top and bottom of the element.", "default": "None"}, {"name": "width", "type": "str | float | None", "description": "The width of the element.", "default": "None"}, {"name": "height", "type": "str | float | None", "description": "The height of the element.", "default": "None"}, {"name": "min_width", "type": "str | float | None", "description": "The minimum width of the element.", "default": "None"}, {"name": "min_height", "type": "str | float | None", "description": "The minimum height of the element.", "default": "None"}, {"name": "max_width", "type": "str | float | None", "description": "The maximum width of the element.", "default": "None"}, {"name": "max_height", "type": "str | float | None", "description": "The maximum height of the element.", "default": "None"}, {"name": "position", "type": "Literal['static', 'relative', 'absolute', 'fixed', 'sticky'] | None", "description": "Specifies how the element is positioned.", "default": "None"}, {"name": "top", "type": "str | float | None", "description": "The distance from the top of the containing element.", "default": "None"}, {"name": "bottom", "type": "str | float | None", "description": "The distance from the bottom of the containing element.", "default": "None"}, {"name": "start", "type": "str | float | None", "description": "The distance from the start of the containing element.", "default": "None"}, {"name": "end", "type": "str | float | None", "description": "The distance from the end of the containing element.", "default": "None"}, {"name": "left", "type": "str | float | None", "description": "The distance from the left of the containing element.", "default": "None"}, {"name": "right", "type": "str | float | None", "description": "The distance from the right of the containing element.", "default": "None"}, {"name": "z_index", "type": "int | None", "description": "The stack order of the element.", "default": "None"}, {"name": "is_hidden", "type": "bool | None", "description": "Whether the element is hidden.", "default": "None"}, {"name": "id", "type": "str | None", "description": "A unique identifier for the element.", "default": "None"}, {"name": "aria_label", "type": "str | None", "description": "The label for the element.", "default": "None"}, {"name": "aria_labelledby", "type": "str | None", "description": "The id of the element that labels the element.", "default": "None"}, {"name": "aria_describedby", "type": "str | None", "description": "The id of the element that describes the element.", "default": "None"}, {"name": "aria_pressed", "type": "Literal['true', 'false'] | bool | Literal['mixed'] | None", "description": "Whether the element is pressed.", "default": "None"}, {"name": "aria_details", "type": "str | None", "description": "The details for the element.", "default": "None"}, {"name": "UNSAFE_class_name", "type": "str | None", "description": "A CSS class to apply to the element.", "default": "None"}, {"name": "UNSAFE_style", "type": "Dict[str, Any] | None", "description": "A CSS style to apply to the element.", "default": "None"}, {"name": "key", "type": "str | None", "description": "A unique identifier used by React to render elements in a list.", "default": "None"}]}} />
