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
6 changed files with 37 additions and 8 deletions

View File

@@ -297,7 +297,7 @@ pre-commit run eslint # Frontend linting
## Platform-Specific Instructions
- **[CLAUDE.md](CLAUDE.md)** - For Claude/Anthropic tools
- **[.github/copilot-instructions.md](.github/copilot-instructions.md)** - For GitHub Copilot
- **[.github/copilot-instructions.md](.github/copilot-instructions.md)** - For GitHub Copilot
- **[GEMINI.md](GEMINI.md)** - For Google Gemini tools
- **[GPT.md](GPT.md)** - For OpenAI/ChatGPT tools
- **[.cursor/rules/dev-standard.mdc](.cursor/rules/dev-standard.mdc)** - For Cursor editor

View File

@@ -127,7 +127,7 @@ aurora-data-api = ["preset-sqlalchemy-aurora-data-api>=0.2.8,<0.3"]
bigquery = [
"pandas-gbq>=0.35.0",
"sqlalchemy-bigquery>=1.17.0",
"google-cloud-bigquery>=3.42.2",
"google-cloud-bigquery>=3.42.1",
]
clickhouse = ["clickhouse-connect>=1.4.2, <2.0"]
cockroachdb = ["cockroachdb>=0.3.5, <0.4"]
@@ -143,7 +143,7 @@ databricks = [
"databricks-sqlalchemy==1.0.5",
]
datafusion = ["flightsql-dbapi>=0.2.0, <0.3"]
db2 = ["ibm-db-sa<=0.4.4, >=0.4.4"]
db2 = ["ibm-db-sa>0.3.8, <=0.4.4"]
denodo = ["denodo-sqlalchemy>=2.0.5,<2.1.0"]
dremio = ["sqlalchemy-dremio>=1.2.1, <4"]
drill = ["sqlalchemy-drill>=1.1.10, <2"]
@@ -189,7 +189,7 @@ ocient = [
"shapely",
"geojson",
]
oracle = ["oracledb>=4.0.2, <5"]
oracle = ["oracledb>=2.0.0, <5"]
parseable = ["sqlalchemy-parseable>=0.1.6,<0.2.0"]
pinot = ["pinotdb>=5.0.0, <10.0.0"]
playwright = ["playwright>=1.61.0, <2"]

View File

@@ -341,7 +341,7 @@ geopy==2.4.1
# apache-superset
gevent==26.4.0
# via apache-superset
google-api-core==2.33.0
google-api-core==2.23.0
# via
# google-cloud-bigquery
# google-cloud-core
@@ -362,7 +362,7 @@ google-auth-oauthlib==1.2.1
# via
# pandas-gbq
# pydata-google-auth
google-cloud-bigquery==3.42.2
google-cloud-bigquery==3.42.1
# via
# apache-superset
# pandas-gbq

View File

@@ -14,7 +14,7 @@
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License
under the License.
-->
# Change Log

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(