Headache using st.date_input for a date range (2 values)

Hello

I wish to use a st.date_input in the sidebar of a multipage app.

I don’t want to complicate my code.
For example by keeping track of a “how many dates have been provided” till now.

How can I manage to do my business when (only when) the range is fully ready in the widget? And avoid doing anything otherwise?

Thanks for your suggestions, and eventually some code example

Michel

You don’t need to “keep track” of that, you can answer that question just by looking at the value.

Thanks Goyo

You are right.
I just understood how the value argument works.

st.date_input( value=… )

Before, I thought I had to provide the range previously chosen by the user, in order to have that visible when using the widget. This was the cause of my headache!

I just realized that the value parameter doesn’t play any role once the user has already made once a choice. It is just a value to be used until the user makes a choice. The widget caches the user choice. If I don’t care, it is easy.

This is simple.

Thanks

Michel

Hello Goyo

I am back again because I am still stuck in this problem.
I though I had solved it by doing this in the script that populates the sidebar (of any page):

dr = st.date_input("drr, value=allDates, min_value=allDates[0], max_value=allDates[1])

This works nicely for any of the pages of my multipage app.
However, changes to the daterange (dr) on one page doesn’t reflect when another page is activated.
Then the user needs to re-define the daterange (dr) everytimes he looks at another page.
Which is not very user-friendly.

Therefore, I wished to solve this issue by storing the datarange (dr) in the session_state and use it as initial value in the statement above.

I transformed the above statement as follows:

dr = st.date_input("drr, value=dr, min_value=allDates[0], max_value=allDates[1])

And this where my headache came back, exactly as before!
Even staying on one page, it becomes impossible to properly specify a range of date.
Typically it ends up with a 1 day daterange like

(datetime.date(2024, 2, 13), datetime.date(2024, 2, 13))

while the user tried to get this for example:

(datetime.date(2024, 2, 13), datetime.date(2024, 2, 17))

This why I wanted to control what happens when the widget state evolves.

I don’t know how to solve that.
I don’t know how have one single sidebar in a multipage app, while keep the st.date_input “synchronized” between pages.

Any suggestion?

Thanks

Michel

dr is a module-level variable so it is not shared among pages. Use session_state to shate state.

Thanks Goyo

This what I did.
I indeed stored dr in session_state.
I did not show in the my answer that dr is first retrieved from session_state.
When switching from one page to another, the state for the date_input is maintained.

But the problem is that it is impossible to define a range properly.
It always ends up with start and end date being the same.

I can guess more or less what happens, but I cannot solve it.
When the user clicks the start date, the dr tuple contains only one item.

The date_input widget is then initialized with a one element widget.
When the user clicks the end date, then de dr tuple contains twice the start date. Not clear why.

Writing this just gave me some idea.
Let’s see.

Thanks

Michel

I am not sure I understand you. If I select 2024-2-13 and 2024-2-17 in the date picker, then I get (datetime.date(2024, 2, 13), datetime.date(2024, 2, 17)) as the return value. Anything else would be a bug.

When the user clicks the start date, the dr tuple contains only one item.

That is correct.

The date_input widget is then initialized with a one element widget.
When the user clicks the end date, then de dr tuple contains twice the start date. Not clear why.

This shouldn’t happen and certainly doesn’t happen for me.

Thanks Goyo

A slight change to the example from the documentation suited my needs.
Below is the modified example.

I need to keep the same date range in the sidebar for all the pages of my app.
But a change to the date range needs to be taken into account only
when st.date_input really returns a range. Otherwise, the app should wait next iteration.

This tiny thing was the reason of my headache!
Probably because it was entangled with the rest of the logic, other parameters that may need to trigger some calculations.

I still have to get used to the Streamlit’s execution model !

Thanks again, you helped a lot!

Michel

PS:
By the way, if the app execution was delayed untils the date_input actually returned a range (as specified in the initial value), I would even not have had to care!
Would it be difficult to create a date_range_input widget, or the like?
But adding “if len(d)==2:” was not too difficult !!!

Modified example for a date range using st.date_input

import streamlit as st
from datetime import date

jan_1, dec_31  = (date(2023, 1, 1), date(2023, 12, 31))
jan_rg = (date(2023, 1, 13), date(2023, 1, 17))

if st.session_state:
    d = st.session_state.d
else:
    d = jan_rg

d = st.date_input("Select a range", value=d, min_value=jan_1, max_value=dec_31)

if len(d)==2:
    st.session_state.d = d
    
d

But there are legitimate use cases where having 1 or 0 values is meaningful. It is up to the app to decide what values have a meaning and what to do with values that don’t.

Yes, I understand.
But this could be an option argument.
Or it could be decided based on the value which is passed initially.
It is not important.

Hello Goyo

Just had the idea that using on_change could help.
Would it be possible to use the callback to practically have date_input behaving exactly as a date_range_input widget?

Thanks

Michel

I don’t see how. You can wrap your code in a function instead.

1 Like