This is an automated email from the ASF dual-hosted git repository. rusackas pushed a commit to branch fix-samples-mismatched-dttm-format in repository https://gitbox.apache.org/repos/asf/superset.git
commit 339d6cac7079eb68c12d9595d3699d703be1dba8 Author: Claude Code <[email protected]> AuthorDate: Fri Jul 24 19:46:11 2026 -0700 fix(core): don't blank a datetime column when its format coerces every value to NaT When a temporal column's declared python_date_format doesn't match its actual data, pd.to_datetime(..., errors='coerce') returns all-NaT and the result was assigned unconditionally, silently nulling the whole column. This surfaces in the Samples/drill-detail pane, where a chart's granularity column (e.g. an epoch-millis 'year' that inherited a '%Y' string format) renders as N/A for every row. Keep the original values when a format coerces every non-null entry to NaT. Co-Authored-By: Claude Fable 5 <[email protected]> --- superset/utils/core.py | 15 ++++++++++++++- tests/unit_tests/utils/test_core.py | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/superset/utils/core.py b/superset/utils/core.py index 5810beba34a..09129c64eed 100644 --- a/superset/utils/core.py +++ b/superset/utils/core.py @@ -2017,13 +2017,26 @@ def _process_datetime_column( # Parse with or without format (suppress warning if no format) if format_to_use: - df[col.col_label] = pd.to_datetime( + converted = pd.to_datetime( df[col.col_label], utc=False, format=format_to_use, errors="coerce", exact=False, ) + # A format that coerces every non-null value to NaT is a mismatch + # (e.g. an epoch-millis column that inherited a '%Y' string format + # when used as a chart's granularity). Assigning it would silently + # blank the whole column, so keep the original values instead. + if df[col.col_label].notna().any() and not converted.notna().any(): + logger.warning( + "Datetime format %s coerced every value of column %s to NaT; " + "keeping the original values", + format_to_use, + col.col_label, + ) + else: + df[col.col_label] = converted else: with warnings.catch_warnings(): warnings.filterwarnings("ignore", message=".*Could not infer format.*") diff --git a/tests/unit_tests/utils/test_core.py b/tests/unit_tests/utils/test_core.py index 9a33ee55ed4..7fe3eee7eff 100644 --- a/tests/unit_tests/utils/test_core.py +++ b/tests/unit_tests/utils/test_core.py @@ -273,6 +273,22 @@ def test_normalize_dttm_col() -> None: assert df["__time"].astype(str).tolist() == ["2017-07-01"] +def test_normalize_dttm_col_mismatched_format_keeps_values() -> None: + """A datetime format that coerces every value to NaT is a mismatch (e.g. an + epoch-millis column that inherited a ``%Y`` string format when used as a + chart's granularity); applying it would silently blank the whole column, so + the original values are kept instead of being nulled. Regression for the + Samples pane showing N/A for such columns.""" + df = pd.DataFrame({"year": [1136073600000, 473385600000]}) # epoch ms + before = df["year"].tolist() + + normalize_dttm_col(df, (DateColumn(col_label="year", timestamp_format="%Y"),)) + + # not blanked to NaT/None + assert df["year"].notna().all() + assert df["year"].tolist() == before + + def test_normalize_dttm_col_epoch_seconds() -> None: """Test conversion of epoch seconds.""" df = pd.DataFrame(
