Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Javascript DatePicker Implementation #667

Merged
merged 49 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6d8433e
ui.date_picker JS side implementation
dgodinez-dh Jul 24, 2024
72fdecf
install internationalized date
dgodinez-dh Jul 25, 2024
70a3886
deserialize value props
dgodinez-dh Jul 25, 2024
f5b48ef
isUnavailable callback
dgodinez-dh Jul 25, 2024
73131cc
review suggestions and unit test
dgodinez-dh Jul 25, 2024
8f86f73
fix nullable types
dgodinez-dh Jul 26, 2024
1bc8a2b
make component annotation
dgodinez-dh Jul 29, 2024
5f7663d
dates to iso strings
dgodinez-dh Jul 29, 2024
fbdf5c8
add is_instant_string
dgodinez-dh Jul 30, 2024
924c795
use time zone setting
dgodinez-dh Jul 30, 2024
863e20d
update unit tests
dgodinez-dh Jul 30, 2024
cbf58d2
comment out unavailable values
dgodinez-dh Jul 30, 2024
79b1ee4
doc updates
dgodinez-dh Jul 30, 2024
438e1ba
merge latest
dgodinez-dh Jul 30, 2024
c0ca90e
fix granularity
dgodinez-dh Jul 30, 2024
0d6f1a9
date picker doc
dgodinez-dh Jul 30, 2024
e3bf54b
remove commented code
dgodinez-dh Jul 30, 2024
8e9be6c
fix python test
dgodinez-dh Jul 30, 2024
25a92ea
update test images
dgodinez-dh Jul 31, 2024
8461dce
update docs
dgodinez-dh Aug 1, 2024
4480e6b
merge latest
dgodinez-dh Aug 5, 2024
150db78
Update plugins/ui/docs/components/date_picker.md
dgodinez-dh Aug 5, 2024
9e87c6a
Update plugins/ui/src/js/src/elements/hooks/useDatepickerProps.ts
dgodinez-dh Aug 5, 2024
c489445
Merge branch 'dag_DatePickerUi' of https://github.com/dgodinez-dh/dee…
dgodinez-dh Aug 5, 2024
fe4f2f0
fix onChange called on mount
dgodinez-dh Aug 5, 2024
e6650b3
fix error in log while typing
dgodinez-dh Aug 5, 2024
82c1425
add issue number for unavailable dates
dgodinez-dh Aug 5, 2024
2733b1e
add time table example
dgodinez-dh Aug 5, 2024
53a50cd
Update plugins/ui/docs/components/date_picker.md
dgodinez-dh Aug 6, 2024
f9b4978
Update plugins/ui/docs/components/date_picker.md
dgodinez-dh Aug 6, 2024
44b6ce5
Update plugins/ui/docs/components/date_picker.md
dgodinez-dh Aug 6, 2024
e5d65e1
Update plugins/ui/docs/components/date_picker.md
dgodinez-dh Aug 6, 2024
b1c1fba
Update plugins/ui/docs/components/date_picker.md
dgodinez-dh Aug 6, 2024
4784e12
fix wrapped date callable
dgodinez-dh Aug 6, 2024
afdf9a4
update docs
dgodinez-dh Aug 7, 2024
d97643e
merge latest
dgodinez-dh Aug 7, 2024
84ea457
more doc updates
dgodinez-dh Aug 7, 2024
3b1bc5e
string parsing defaults to zdt
dgodinez-dh Aug 7, 2024
a68597d
fix unit test
dgodinez-dh Aug 8, 2024
ea349ce
more doc updates
dgodinez-dh Aug 8, 2024
dac5a6f
more examples
dgodinez-dh Aug 8, 2024
c2f00c5
doc example fixes
dgodinez-dh Aug 8, 2024
4fd4365
merge latest
dgodinez-dh Aug 19, 2024
910d544
server side placeholder default
dgodinez-dh Aug 19, 2024
5462e82
merge latest
dgodinez-dh Aug 19, 2024
7a86f21
handle time zone for uncontrolled date pickers
dgodinez-dh Aug 19, 2024
183b620
update images
dgodinez-dh Aug 19, 2024
9c6a007
Revert "update images"
dgodinez-dh Aug 19, 2024
c399f1c
update images
dgodinez-dh Aug 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 30 additions & 21 deletions plugins/ui/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -1020,11 +1020,12 @@ A tabs component can be used to organize content in a collection of tabs, allowi

###### Parameters

| Parameter | Type | Description |
| ----------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `*children` | `Tab \| TabList \| TabPanels` | The tab panels to render within the tabs component. |
| `on_change` | `Callable[[Key], None] \| None` | Alias of `on_selection_change`. Handler that is called when the tab selection changes. |
| `**props` | `Any` | Any other [Tabs](https://react-spectrum.adobe.com/react-spectrum/Tabs.html#tabs-props) prop
| Parameter | Type | Description |
| ----------- | ------------------------------- | ------------------------------------------------------------------------------------------- |
| `*children` | `Tab \| TabList \| TabPanels` | The tab panels to render within the tabs component. |
| `on_change` | `Callable[[Key], None] \| None` | Alias of `on_selection_change`. Handler that is called when the tab selection changes. |
| `**props` | `Any` | Any other [Tabs](https://react-spectrum.adobe.com/react-spectrum/Tabs.html#tabs-props) prop |

|

|
Expand Down Expand Up @@ -1314,14 +1315,24 @@ list_view5 = ui.list_view(

A date picker that can be used to select a date.

There are three types that can be passed in to the props that control the date format:
The date picker 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".
This will create a date picker with a granularity of days.
2. `Instant`: An Instant represents an unambiguous specific point on the timeline, such as 2021-04-12T14:13:07 UTC.
This will create a date picker with a granularity of seconds in UTC.
This will create a date picker with a granularity of seconds in UTC. The time zone will be rendered as the time zone in user settings.
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.
This will create a date picker with a granularity of seconds in the specified time zone.
This will create a date picker with a granularity of seconds in the specified time zone. The time zone will be rendered as the specified time zone.

The input is coverted according to the following rules:

1. If the input is one of the three Java date types, use that type.
2. A date string such as "2007-12-03" will parse to a `LocalDate`
3. A string with a date, time, and timezone such as "2021-04-12T14:13:07 America/New_York" will parse to a `ZonedDateTime`
4. All other types will attempt to convert in this order: `Instant`, `ZonedDateTime`, `LocalDate`

The format of the date picker 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:
Expand All @@ -1340,7 +1351,6 @@ ui.date_picker(
default_value: Date | None = None,
min_value: Date | None = None,
max_value: Date | None = None,
unavailable_values: Sequence[Date] | None = None,
granularity: Granularity | None = None,
on_change: Callable[[Date], None] | None = None,
**props: Any
Expand All @@ -1349,24 +1359,23 @@ ui.date_picker(

###### Parameters

| Parameter | Type | Description |
| -------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `placeholder_value` | `Date \| None` | A placeholder date that influences the format of the placeholder shown when no value is selected. Defaults to today at midnight in the user's timezone. |
| `value` | `Date \| None` | The current value (controlled). |
| `default_value` | `Date \| None` | The default value (uncontrolled). |
| `min_value` | `Date \| None` | The minimum allowed date that a user may select. |
| `max_value` | `Date \| None` | The maximum allowed date that a user may select. |
| `unavailable_values` | `Sequence[Date] \| None` | A list of dates that cannot be selected. |
| `granularity` | `Granularity \| None` | Determines the smallest unit that is displayed in the date picker. By default, this is `"DAY"` for `LocalDate`, and `"SECOND"` otherwise. |
| `on_change` | `Callable[[Date], None] \| None` | Handler that is called when the value changes. The exact `Date` type will be the same as the type passed to `value`, `default_value` or `placeholder_value`, in that order of precedence. |
| `**props` | `Any` | Any other [DatePicker](https://react-spectrum.adobe.com/react-spectrum/DatePicker.html) prop, with the exception of `isDateUnavailable`, `validate`, and `errorMessage` (as a callback) |
| Parameter | Type | Description |
| ------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
| `placeholder_value` | `Date \| None` | A placeholder date that influences the format of the placeholder shown when no value is selected. Defaults to today at the current time on the server machine time zone. |
| `value` | `Date \| None` | The current value (controlled). |
| `default_value` | `Date \| None` | The default value (uncontrolled). |
| `min_value` | `Date \| None` | The minimum allowed date that a user may select. |
| `max_value` | `Date \| None` | The maximum allowed date that a user may select. | |
| `granularity` | `Granularity \| None` | Determines the smallest unit that is displayed in the date picker. By default, this is `"DAY"` for `LocalDate`, and `"SECOND"` otherwise. |
| `on_change` | `Callable[[Date], None] \| None` | Handler that is called when the value changes. The exact `Date` type will be the same as the type passed to `value`, `default_value` or `placeholder_value`, in that order of precedence. |
| `**props` | `Any` | Any other [DatePicker](https://react-spectrum.adobe.com/react-spectrum/DatePicker.html) prop, with the exception of `isDateUnavailable`, `validate`, and `errorMessage` (as a callback) |

```py

import deephaven.ui as ui
from deephaven.time import to_j_local_date, dh_today, to_j_instant, to_j_zdt

zoned_date_time = to_j_zdt("1995-03-22T11:11:11.23142 UTC")
zoned_date_time = to_j_zdt("1995-03-22T11:11:11.23142 America/New_York")
instant = to_j_instant("2022-01-01T00:00:00 ET")
local_date = to_j_local_date(dh_today())

Expand Down
172 changes: 172 additions & 0 deletions plugins/ui/docs/components/date_picker.md
Copy link
Member

Choose a reason for hiding this comment

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

This interface feels like it has an incomplete integration with the existing time libraries. In particular, business calendars are not supported. Business calendars could be used to limit selected dates or times, they can be used to get a appropriate time zone, etc. Business calendars could be added as a future enhancement, but I wanted to note it here.
https://deephaven.io/core/pydoc/code/deephaven.calendar.html
https://github.com/deephaven/deephaven-core/blob/main/engine/time/src/main/java/io/deephaven/time/calendar/BusinessCalendar.java

Copy link
Member

Choose a reason for hiding this comment

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

We're looking at adding the unavailable dates afterwards: #698
Didn't want that to hold up the the rest of the date picker.

Copy link
Member

Choose a reason for hiding this comment

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

date_picker interprets date-time strings in a way that is inconsistent with all other DH time libraries. For example, it interprets any UTC string only as an Instant. All other time libraries accept the same date-time format for either an Instant or a ZonedDateTime. The inconsistency introduced by date_picker is not good and will be confusing to explain and support. I think the inconsistency can be resolved cleanly by introducing a date_type or string_format parameter that can take values of LocalDate, Instant, and ZonedDateTime. This could then be used to control how the string is parsed.

Note: I have a PR to introduce a new format string that is ZDT specific, but there has been enough arguing that I have not pushed it forward. deephaven/deephaven-core#5069

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Per discussion, we are going to default datetime strings to ZDT. Then update the documentation to clarify which value input types will produce which output types.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think the widget has a way to select a time zone via clicking. I can imagine that this would be useful. The adobe docs don't seem to mention it, so it might not be available.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the DatePicker will display a timezone but does not allow to change it.
For the current implementation, the DatePicker will display the TZ from the user settings and update if the user changes the TZ in the settings. This is for an Instant. If the user uses a ZDT, then the DatePicker is locked to the TZ provided by the ZDT.

Copy link
Member

Choose a reason for hiding this comment

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

The key java time type that is not included in this widget is LocalTime (https://deephaven.io/core/docs/conceptual/time-in-deephaven/#natively-supported-date-time-types). Should it be supported in this widget or in a different one?

Copy link
Member

Choose a reason for hiding this comment

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

There is a question about what return values should result from string inputs. There was a very long discussion around this topic when we created the query language time and calendar library. Ultimately, we decided to make everything as java time centric as possible. As a result, almost all return types are Java time types. There are a few cases where local-date-strings can be provided as inputs and they will return local-date-strings as outputs, but I think these were only included to make it easier to work with tables using strings as date columns -- which is very common.

As a result, I think it is least confusing to always return a java time value. If there is a compelling case to do something different, we should discuss that specific case.

Having said that, it doesn't look like we have exposed the string formatting methods (e.g. https://github.com/deephaven/deephaven-core/blob/main/engine/time/src/main/java/io/deephaven/time/DateTimeUtils.java#L4273) in python. (https://deephaven.io/core/pydoc/code/deephaven.time.html). Do we need to do this?

Notes:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Per discussion, we are going to stick with Java date types. User can convert to python / strings as needed.

Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Date Picker

Date Pickers allow users to select a Date and Time from a pop up Calendar.

## Example

```python
from deephaven import ui

dp = ui.date_picker(
label="Date Picker",
default_value="2024-01-02T10:30:00 UTC",
on_change=print,
)
mofojed marked this conversation as resolved.
Show resolved Hide resolved
```

## Date types

The date picker 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".
This will create a date picker with a granularity of days.
2. `Instant`: An Instant represents an unambiguous specific point on the timeline, such as 2021-04-12T14:13:07 UTC.
This will create a date picker with a granularity of seconds in UTC. The time zone will be rendered as the time zone in user settings.
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.
This will create a date picker with a granularity of seconds in the specified time zone. The time zone will be rendered as the specified time zone.

The input is coverted according to the following rules:

1. If the input is one of the three Java date types, use that type.
2. A date string such as "2007-12-03" will parse to a `LocalDate`
3. A string with a date, time, and timezone such as "2021-04-12T14:13:07 America/New_York" will parse to a `ZonedDateTime`
4. All other types will attempt to convert in this order: `Instant`, `ZonedDateTime`, `LocalDate`

The format of the date picker 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. `placeholder_value`
Comment on lines +41 to +43
Copy link
Member

Choose a reason for hiding this comment

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

Do they get an error if value and default_value are inconsistent types?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried this in various permutations.
There is no error. Instead one of the types "wins" in order of value, default_value, placeholder_value
The docs discuss this as on_change being determined in that precedence.

Comment on lines +41 to +43
Copy link
Member

Choose a reason for hiding this comment

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

It would be good to have more specifics on these parameters here. They are included in DESIGN.md. For example, knowing the default values is critical to know.

Copy link
Member

Choose a reason for hiding this comment

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

In DESIGN.md, the default is described as Defaults to today at midnight in the user's timezone. I assume that doc should also indicate that it is a ZonedDateTime. There is an enormous amount of ambiguity in what the "user's timezone" is. Possibilities are:

  1. The local machine time zone.
  2. The Java default time zone.
  3. The time zone of the default calendar.
  4. The time zone configured for rendering in the UI.

There needs to be a discussion about which is most appropriate and for what reason. The docs then need to be very clear on the default.

Notes:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This comes from the Spectrum docs:

A placeholder date that influences the format of the placeholder shown when no value is selected. Defaults to today's date at midnight.

So it must be defaulting to the local machine time zone.


If none of these are provided, the `on_change` handler will be passed an `Instant`.

## Controlled mode with value

Setting the `value` prop will put the date_picker in controlled mode. Selecting a new date will call the `on_change` callback.
Then `value` must be updated programatically to render the new value. This can be done using the `use_state` hook.

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

zoned_date_time = to_j_zdt("1995-03-22T11:11:11.23142 America/New_York")
instant = to_j_instant("2022-01-01T00:00:00 ET")
local_date = to_j_local_date(dh_today())


@ui.component
def date_picker_test(value):
date, set_date = ui.use_state(value)
return [ui.date_picker(on_change=set_date, value=date), ui.text(str(date))]


zoned_date_picker = date_picker_test(zoned_date_time)
instant_date_picker = date_picker_test(instant)
local_date_picker = date_picker_test(local_date)
```

## Uncontrolled mode with default_value

If the `value` prop is omitted, the date_picker will be in uncontrolled mode. It will store its state internally and automatically update when a new date is selected.
In this mode, setting the `default_value` prop will determine the initial value displayed by the date_picker.

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

dp = ui.date_picker(
label="Date Picker",
default_value=dh_now(),
on_change=print,
)
```

## Uncontrolled mode with placeholder_value

If both `value` and `default_value` are omitted, the date_picker will be in uncontrolled mode displaying no date selected. When opened, the date picker will suggest the date from the `placeholder_value` prop.
Omitting `placeholder_value` will default it to today at the current time on the server machine time zone.

```python
from deephaven import ui

dp1 = ui.date_picker(
label="Date Picker",
placeholder_value="2022-10-01T08:30:00 ET",
on_change=print,
)

dp2 = ui.date_picker(
label="Date Picker",
on_change=print,
)
```

## Events

Date Pickers accept a value to display and can trigger actions based on events such as setting state when changed. See the [API Reference](#api-reference) for a full list of available events.

## Variants

Date Pickers can have different variants to indicate their purpose.

```python
from deephaven import ui


@ui.component
def date_picker_variants():
return [
ui.date_picker(description="description"),
ui.date_picker(error_message="error", validation_state="valid"),
ui.date_picker(error_message="error", validation_state="invalid"),
ui.date_picker(min_value="2024-01-01", max_value="2024-01-05"),
ui.date_picker(value="2024-07-27T16:10:10 America/New_York", hour_cycle=24),
ui.date_picker(granularity="YEAR"),
ui.date_picker(granularity="MONTH"),
ui.date_picker(granularity="DAY"),
ui.date_picker(granularity="HOUR"),
ui.date_picker(granularity="MINUTE"),
ui.date_picker(granularity="SECOND"),
]


date_picker_variants_example = date_picker_variants()
```
mofojed marked this conversation as resolved.
Show resolved Hide resolved
Comment on lines +112 to +138
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be split into timezone, validation and granulariy sections.

Docs are expected to be exhaustive and there are several concepts and sections from: https://react-spectrum.adobe.com/react-spectrum/DatePicker.html that are missing


## Time table filtering

Date Pickers 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"):
after_date, set_after_date = ui.use_state(start_date)
before_date, set_before_date = ui.use_state(end_date)
return [
ui.date_picker(label="Start Date", value=after_date, on_change=set_after_date),
ui.date_picker(label="End Date", value=before_date, on_change=set_before_date),
table.where(f"{time_col} >= after_date && {time_col} < before_date"),
]


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

```{eval-rst}
.. dhautofunction:: deephaven.ui.date_picker
```
Loading
Loading