fix: DateRangeSlider arbitrarily changes both values with timezone-aware timestamps - #8455
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8455 +/- ##
==========================================
- Coverage 86.15% 86.05% -0.10%
==========================================
Files 349 349
Lines 55096 55153 +57
==========================================
- Hits 47468 47464 -4
- Misses 7628 7689 +61 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Thank you @SuMayaBee! Your fix for bug 2 looks good however for:
This behavior is intended. We have long decided that we would never perform any timezone conversions internally. So we always treat all times as UTC and leave it up to the user to define the time in their timezone. |
Thanks @philippjfr for the clarification! I've reverted the fix for Bug 1 datetime_as_utctimestamp change and updated the tests accordingly. The Bug 2 fix for explicit start/end conversion is still in place. |
|
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
What was the problem?
While checking issue #7091, I tested it myself and found the issue still persists. When using
pn.widgets.DateRangeSliderwith timezone-awarepd.Timestampvalues (e.g.,+0200offset), moving one handle would also shift the other. Both handles moved together, making the widget unusable for this use case.Changing one handle would drag the other along with it. I've included before and after UI screen recordings to demonstrate the fix.
Before:
After:
2026-03-02.00-12-54.mp4
What was causing it?
I traced the issue to two bugs working together.
Bug 1 — Wrong UTC conversion in
datetime_as_utctimestamp(panel/util/__init__.py)When Panel converts a datetime to the millisecond timestamp that Bokeh uses internally, it calls
datetime_as_utctimestamp. For timezone-aware datetimes, the old code did this:The problem is that
.replace()just swaps the timezone label without actually shifting the time. So2024-04-29 15:30:00+0200(which is13:30:00 UTC) was being treated as2024-04-29 15:30:00 UTC— 2 hours ahead of the correct UTC time. This causedvalue[1]to be sent to Bokeh 2 hours beyondend, so Bokeh clamped the handle.Bug 2 —
startandendwere not explicitly converted (panel/widgets/slider.py)In
DateRangeSlider._process_param_change,valuewas explicitly converted throughdatetime_as_utctimestamp, butstartandendwere passed raw to Bokeh, which used its own internal serialization. Even after fixing Bug 1, any difference between these two conversion paths could leavevalueslightly outside[start, end], causing Bokeh to clamp the handles again.How I solved it
Fix 1 —
panel/util/__init__.pyUsed
.timestamp()directly for timezone-aware datetimes, which correctly returns UTC epoch seconds accounting for the offset:Fix 2 —
panel/widgets/slider.pyAdded explicit conversion for
startandendin_process_param_change, so all four values (start,end,value[0],value[1]) go through the exact same UTC conversion:Tests added
test_date_range_slider_timezone_aware_value— regression test: verifies thatvalue,start, andendare all correctly converted and consistent in the Bokeh model when using timezone-aware timestamps.test_date_range_slider_start_end_explicit_conversion— verifies thatstartandendare explicitly converted throughdatetime_as_utctimestamp, matchingvalue[0]andvalue[1]exactly.test_datetime_as_utctimestamp_timezone_aware— unit test for the utility function: confirms that naive datetimes are treated as UTC, and timezone-aware datetimes are correctly converted (not just relabeled).AI Disclosure
AI Disclosure: I used Gemini 3 Flash to explore and understand the codebase while working on this fix.
Closes #7091