Skip to content

fix: DateRangeSlider arbitrarily changes both values with timezone-aware timestamps - #8455

Merged
philippjfr merged 4 commits into
holoviz:mainfrom
SuMayaBee:fix-DateRangeSlider-changes-both-values
Mar 12, 2026
Merged

fix: DateRangeSlider arbitrarily changes both values with timezone-aware timestamps#8455
philippjfr merged 4 commits into
holoviz:mainfrom
SuMayaBee:fix-DateRangeSlider-changes-both-values

Conversation

@SuMayaBee

Copy link
Copy Markdown
Contributor

What was the problem?

While checking issue #7091, I tested it myself and found the issue still persists. When using pn.widgets.DateRangeSlider with timezone-aware pd.Timestamp values (e.g., +0200 offset), 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:

355815364-23dceab9-7d48-4e3a-aa39-58fb5bcaa959

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:

# OLD (wrong)
return value.replace(tzinfo=dt.timezone.utc).timestamp() * 1000

The problem is that .replace() just swaps the timezone label without actually shifting the time. So 2024-04-29 15:30:00+0200 (which is 13:30:00 UTC) was being treated as 2024-04-29 15:30:00 UTC — 2 hours ahead of the correct UTC time. This caused value[1] to be sent to Bokeh 2 hours beyond end, so Bokeh clamped the handle.

Bug 2 — start and end were not explicitly converted (panel/widgets/slider.py)

In DateRangeSlider._process_param_change, value was explicitly converted through datetime_as_utctimestamp, but start and end were passed raw to Bokeh, which used its own internal serialization. Even after fixing Bug 1, any difference between these two conversion paths could leave value slightly outside [start, end], causing Bokeh to clamp the handles again.

How I solved it

Fix 1 — panel/util/__init__.py

Used .timestamp() directly for timezone-aware datetimes, which correctly returns UTC epoch seconds accounting for the offset:

def datetime_as_utctimestamp(value):
    """
    Converts a datetime to a UTC timestamp used by Bokeh internally.
    """
    if value.tzinfo is not None:
        return value.timestamp() * 1000
    return value.replace(tzinfo=dt.timezone.utc).timestamp() * 1000

Fix 2 — panel/widgets/slider.py

Added explicit conversion for start and end in _process_param_change, so all four values (start, end, value[0], value[1]) go through the exact same UTC conversion:

for key in ('start', 'end'):
    if key in msg and isinstance(msg[key], dt.datetime):
        msg[key] = datetime_as_utctimestamp(msg[key])

Tests added

  • test_date_range_slider_timezone_aware_value — regression test: verifies that value, start, and end are all correctly converted and consistent in the Bokeh model when using timezone-aware timestamps.
  • test_date_range_slider_start_end_explicit_conversion — verifies that start and end are explicitly converted through datetime_as_utctimestamp, matching value[0] and value[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

@codecov

codecov Bot commented Mar 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.05%. Comparing base (2c7dbc6) to head (0a58ed1).
⚠️ Report is 6 commits behind head on main.

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@SuMayaBee SuMayaBee changed the title Fix: DateRangeSlider arbitrarily changes both values with timezone-aware timestamps fix: DateRangeSlider arbitrarily changes both values with timezone-aware timestamps Mar 2, 2026
@philippjfr

Copy link
Copy Markdown
Member

Thank you @SuMayaBee! Your fix for bug 2 looks good however for:

Bug 1 — Wrong UTC conversion in datetime_as_utctimestamp (panel/util/init.py)

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.

@SuMayaBee

SuMayaBee commented Mar 2, 2026

Copy link
Copy Markdown
Contributor Author

Thank you @SuMayaBee! Your fix for bug 2 looks good however for:

Bug 1 — Wrong UTC conversion in datetime_as_utctimestamp (panel/util/init.py)

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.

@philippjfr
philippjfr merged commit ce75a70 into holoviz:main Mar 12, 2026
17 of 19 checks passed
@github-actions

Copy link
Copy Markdown

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.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Jun 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pn.widgets.DateRangeSlider arbitrarily changes both values

2 participants