Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Code
339d6cac70 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 <noreply@anthropic.com>
2026-07-24 19:46:11 -07:00
2 changed files with 30 additions and 1 deletions

View File

@@ -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.*")

View File

@@ -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(