Fix fill_between TypeError when passed datetime or datetime64 coordinates

AL
3d ago
+511-52

Summary

  • Problem: Calling ax.fill_between(x, y1, y2) with temporal coordinates (datetime.datetime, datetime.date, or datetime64) failed with:
TypeError: float() argument must be a string or a real number, not 'datetime.datetime'

This happened because fill_between passed raw inputs directly to _masked_float(x) without going through unit_converted_values(x) like other coordinate paths in Axes.

  • Solution: Wrapped x in unit_converted_values(x) before calling _masked_float(x) in Axes.fill_between:
xv, upper, lower = np.broadcast_arrays(
    _masked_float(unit_converted_values(x)),
    _masked_float(y1),
    _masked_float(y2),
)

No modification to _masked_float itself was needed; reusing the existing coordinate conversion handles temporal normalization consistently across the module.

  • Validation:
  • Added regression tests in tests/pyplot/test_axes_charts.py verifying that Python datetime lists and NumPy datetime64 arrays convert to expected epoch millisecond coordinates and that masked/NaT inputs break polygon segments properly.
  • Verified local verification gates: make check, make check-pyplot, make check-errors, and make news-check.

Minimal Reproducible Example

Code

from datetime import datetime, timedelta
import xy.pyplot as plt

# Generate temporal coordinates
base_time = datetime(2026, 1, 1, 0, 0)
dates = [base_time + timedelta(days=i) for i in range(5)]
y1 = [10.0, 11.5, 12.0, 14.0, 13.5]
y2 = [8.0, 9.5, 10.0, 11.5, 11.0]

fig, ax = plt.subplots()

# Previously failed with TypeError; now succeeds
ax.fill_between(dates, y1, y2, alpha=0.3, label="Band")
ax.plot(dates, y1, label="Upper")
ax.legend()

Before

Traceback (most recent call last):
  File "main.py", line 13, in <module>
    ax.fill_between(dates, y1, y2, alpha=0.3, label="Band")
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "xy/pyplot/_axes.py", line 3033, in fill_between
    _masked_float(x),
    ~~~~~~~~~~~~~^^^
  File "xy/pyplot/_axes.py", line 9511, in _masked_float
    return np.ma.asarray(value, dtype=np.float64).filled(np.nan)
           ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "numpy/ma/core.py", line 8691, in asarray
    return masked_array(a, dtype=dtype, copy=False, keep_mask=True,
                        subok=False, order=order)
  File "numpy/ma/core.py", line 2903, in __new__
    _data = np.array(data, dtype=dtype, copy=copy,
                     order=order, subok=True, ndmin=ndmin)
TypeError: float() argument must be a string or a real number, not 'datetime.datetime'

After

Executes cleanly and produces the correct area mark with canonical epoch millisecond coordinates.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed fill_between errors with Python datetime, NumPy datetime64, and masked date sequences.
    • Improved datetime-axis handling for regular and masked inputs, including NaT values.
    • Filled regions now break around masked or invalid dates instead of connecting across them.
  • Documentation
    • Clarified that temporal coordinates are converted to epoch milliseconds while datetime-axis behavior is retained.