Skip to content

Commit c8acdf9

Browse files
authored
Format date/time slider URL query params as ISO strings (#14120)
- Date/time/datetime sliders with bind="query-params" now format URL values as human-readable ISO strings (e.g., `?date=2024-06-15`, `?time=14:30`, `?dt=2024-06-15T14:30`) instead of raw microsecond timestamps (e.g., ?`date=1718409600000000`). - The conversion is localized to the URL boundary: the frontend converts micros → ISO when writing to the URL, and the backend parses ISO → micros when reading from the URL. The internal wire format (double_array_value with microsecond floats) is unchanged. - Old bookmarked URLs with raw microsecond values continue to work (backward compatible).
1 parent c0cf390 commit c8acdf9

File tree

14 files changed

+740
-16
lines changed

14 files changed

+740
-16
lines changed
-3 Bytes
Loading
-248 Bytes
Loading
-304 Bytes
Loading
-259 Bytes
Loading

e2e_playwright/st_slider.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from datetime import date, datetime, time
15+
from datetime import date, datetime, time, timedelta
1616

1717
import streamlit as st
1818
from streamlit import runtime
@@ -287,3 +287,71 @@ def test_fragment():
287287
bind="query-params",
288288
)
289289
st.write("Bound range value:", bound_range)
290+
291+
# Slider 30 - Date slider with bind
292+
bound_date = st.slider(
293+
"Bound date slider",
294+
min_value=date(2020, 1, 1),
295+
max_value=date(2025, 12, 31),
296+
value=date(2023, 6, 15),
297+
key="bound_date",
298+
bind="query-params",
299+
)
300+
st.write("Bound date value:", bound_date)
301+
302+
# Slider 31 - Time slider with bind
303+
bound_time = st.slider(
304+
"Bound time slider",
305+
min_value=time(0, 0),
306+
max_value=time(23, 59),
307+
value=time(12, 0),
308+
key="bound_time",
309+
bind="query-params",
310+
)
311+
st.write("Bound time value:", bound_time)
312+
313+
# Slider 32 - Datetime slider with bind
314+
bound_datetime = st.slider(
315+
"Bound datetime slider",
316+
min_value=datetime(2020, 1, 1, 0, 0),
317+
max_value=datetime(2025, 12, 31, 23, 59),
318+
value=datetime(2023, 6, 15, 14, 30),
319+
key="bound_datetime",
320+
bind="query-params",
321+
)
322+
st.write("Bound datetime value:", bound_datetime)
323+
324+
# Slider 33 - Date range slider with bind
325+
bound_date_range = st.slider(
326+
"Bound date range slider",
327+
min_value=date(2020, 1, 1),
328+
max_value=date(2025, 12, 31),
329+
value=(date(2022, 1, 1), date(2024, 1, 1)),
330+
key="bound_date_range",
331+
bind="query-params",
332+
)
333+
st.write("Bound date range value:", bound_date_range)
334+
335+
# Slider 34 - Time slider with second-resolution step and bind
336+
bound_time_secs = st.slider(
337+
"Bound time seconds slider",
338+
min_value=time(0, 0),
339+
max_value=time(23, 59, 59),
340+
value=time(12, 0, 0),
341+
step=timedelta(seconds=30),
342+
key="bound_time_secs",
343+
bind="query-params",
344+
)
345+
st.write("Bound time secs value:", bound_time_secs)
346+
347+
# Slider 35 - Datetime slider with second-resolution step and bind
348+
bound_datetime_secs = st.slider(
349+
"Bound datetime seconds slider",
350+
min_value=datetime(2024, 1, 1, 0, 0, 0),
351+
max_value=datetime(2024, 12, 31, 23, 59, 59),
352+
value=datetime(2024, 6, 15, 14, 30, 0),
353+
step=timedelta(seconds=30),
354+
key="bound_datetime_secs",
355+
bind="query-params",
356+
)
357+
st.write("Bound datetime secs value:", bound_datetime_secs)

e2e_playwright/st_slider_test.py

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
tab_until_focused,
3838
)
3939

40-
NUM_SLIDER_WIDGETS = 30
40+
NUM_SLIDER_WIDGETS = 36
4141

4242

4343
def test_slider_rendering(themed_app: Page, assert_snapshot: ImageCompareFunction):
@@ -258,8 +258,8 @@ def test_slider_works_with_fragments(app: Page):
258258
def test_slider_with_float_formatting(app: Page, assert_snapshot: ImageCompareFunction):
259259
slider = get_slider(app, "Slider 11 (formatted float)")
260260
slider.hover()
261-
# click in middle
262261
app.mouse.down()
262+
app.mouse.up()
263263

264264
# Move slider once to right
265265
app.keyboard.press("ArrowRight")
@@ -487,3 +487,127 @@ def test_slider_query_param_empty_value_rejected(page: Page, app_base_url: str):
487487
# Slider should use default (50), empty param should be cleared
488488
expect_prefixed_markdown(page, "Bound int value:", "50")
489489
expect(page).not_to_have_url(re.compile(r"[?&]bound_int="))
490+
491+
492+
# --- Date/time/datetime slider ISO URL tests ---
493+
494+
495+
def test_slider_query_param_date_iso_seeding(page: Page, app_base_url: str):
496+
"""Test that a date slider can be seeded with an ISO date string."""
497+
page.goto(build_app_url(app_base_url, query={"bound_date": "2024-03-20"}))
498+
wait_for_app_loaded(page)
499+
500+
expect_prefixed_markdown(page, "Bound date value:", "2024-03-20")
501+
expect(page).to_have_url(re.compile(r"bound_date=2024-03-20"))
502+
503+
504+
def test_slider_query_param_time_iso_seeding(page: Page, app_base_url: str):
505+
"""Test that a time slider can be seeded with an ISO time string."""
506+
page.goto(build_app_url(app_base_url, query={"bound_time": "09:30"}))
507+
wait_for_app_loaded(page)
508+
509+
expect_prefixed_markdown(page, "Bound time value:", "09:30:00")
510+
expect(page).to_have_url(re.compile(r"bound_time=09%3A30"))
511+
512+
513+
def test_slider_query_param_datetime_iso_seeding(page: Page, app_base_url: str):
514+
"""Test that a datetime slider can be seeded with an ISO datetime string."""
515+
page.goto(build_app_url(app_base_url, query={"bound_datetime": "2024-03-20T09:30"}))
516+
wait_for_app_loaded(page)
517+
518+
expect_prefixed_markdown(page, "Bound datetime value:", "2024-03-20 09:30:00")
519+
expect(page).to_have_url(re.compile(r"bound_datetime=2024-03-20T09%3A30"))
520+
521+
522+
def test_slider_query_param_date_range_iso_seeding(page: Page, app_base_url: str):
523+
"""Test that a date range slider can be seeded with ISO date strings."""
524+
page.goto(
525+
build_app_url(
526+
app_base_url,
527+
query={"bound_date_range": ["2021-06-01", "2023-12-15"]},
528+
)
529+
)
530+
wait_for_app_loaded(page)
531+
532+
expect_prefixed_markdown(
533+
page,
534+
"Bound date range value:",
535+
"(datetime.date(2021, 6, 1), datetime.date(2023, 12, 15))",
536+
)
537+
expect(page).to_have_url(
538+
re.compile(r"bound_date_range=2021-06-01&bound_date_range=2023-12-15")
539+
)
540+
541+
542+
def test_slider_query_param_date_default_not_in_url(app: Page):
543+
"""Test that a date slider at its default value does not show in URL."""
544+
expect(app).not_to_have_url(re.compile(r"[?&]bound_date="))
545+
546+
547+
def test_slider_query_param_date_invalid_iso_resets(page: Page, app_base_url: str):
548+
"""Test that an invalid ISO date resets the slider to default."""
549+
page.goto(build_app_url(app_base_url, query={"bound_date": "not-a-date"}))
550+
wait_for_app_loaded(page)
551+
552+
expect_prefixed_markdown(page, "Bound date value:", "2023-06-15")
553+
expect(page).not_to_have_url(re.compile(r"[?&]bound_date="))
554+
555+
556+
def test_slider_query_param_date_updates_url_with_iso(app: Page):
557+
"""Test that interacting with a date slider updates the URL with ISO format."""
558+
slider = get_element_by_key(app, "bound_date")
559+
slider.get_by_role("slider").press("ArrowRight")
560+
wait_for_app_run(app)
561+
562+
expect_prefixed_markdown(app, "Bound date value:", "2023-06-16")
563+
expect(app).to_have_url(re.compile(r"bound_date=2023-06-16"))
564+
565+
566+
def test_slider_query_param_time_updates_url_with_iso(app: Page):
567+
"""Test that interacting with a time slider updates the URL with ISO format."""
568+
slider = get_element_by_key(app, "bound_time")
569+
slider.get_by_role("slider").press("ArrowRight")
570+
wait_for_app_run(app)
571+
572+
expect_prefixed_markdown(app, "Bound time value:", "12:15:00")
573+
expect(app).to_have_url(re.compile(r"bound_time=12%3A15"))
574+
575+
576+
def test_slider_query_param_datetime_updates_url_with_iso(app: Page):
577+
"""Test that interacting with a datetime slider updates the URL with ISO format.
578+
579+
The default (2023-06-15 14:30) is between step boundaries (step=1day from
580+
midnight). BaseWeb quantizes to the nearest boundary on interaction, so
581+
ArrowRight produces 2023-06-17 00:00 rather than 2023-06-16 14:30.
582+
"""
583+
slider = get_element_by_key(app, "bound_datetime")
584+
slider.get_by_role("slider").press("ArrowRight")
585+
wait_for_app_run(app)
586+
587+
expect_prefixed_markdown(app, "Bound datetime value:", "2023-06-17 00:00:00")
588+
expect(app).to_have_url(re.compile(r"bound_datetime=2023-06-17T00%3A00"))
589+
590+
591+
# --- Second-resolution slider tests ---
592+
593+
594+
def test_slider_query_param_time_seconds_iso_seeding(page: Page, app_base_url: str):
595+
"""Test that a time slider with seconds-step can be seeded with HH:MM:SS."""
596+
page.goto(build_app_url(app_base_url, query={"bound_time_secs": "09:30:30"}))
597+
wait_for_app_loaded(page)
598+
599+
expect_prefixed_markdown(page, "Bound time secs value:", "09:30:30")
600+
expect(page).to_have_url(re.compile(r"bound_time_secs=09%3A30%3A30"))
601+
602+
603+
def test_slider_query_param_datetime_seconds_iso_seeding(page: Page, app_base_url: str):
604+
"""Test that a datetime slider with seconds-step can be seeded with seconds."""
605+
page.goto(
606+
build_app_url(
607+
app_base_url, query={"bound_datetime_secs": "2024-03-20T09:30:30"}
608+
)
609+
)
610+
wait_for_app_loaded(page)
611+
612+
expect_prefixed_markdown(page, "Bound datetime secs value:", "2024-03-20 09:30:30")
613+
expect(page).to_have_url(re.compile(r"bound_datetime_secs=2024-03-20T09%3A30%3A30"))

0 commit comments

Comments
 (0)