This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 3ccab2ea7aa fix(time-format): share DateWithFormatter and stop reading 
four-digit years as epoch offsets (#43909)
3ccab2ea7aa is described below

commit 3ccab2ea7aa32fe29221cfe4e1ee68da64eec912
Author: Evan Rusackas <[email protected]>
AuthorDate: Tue Sep 8 13:24:43 2026 -0700

    fix(time-format): share DateWithFormatter and stop reading four-digit years 
as epoch offsets (#43909)
    
    Co-authored-by: Claude Fable 5.1 <[email protected]>
---
 .../src/time-format}/DateWithFormatter.ts          | 13 +++--
 .../superset-ui-core/src/time-format/index.ts      |  1 +
 .../src/time-format/utils/stringifyTimeInput.ts    | 15 +++++-
 .../test/time-format}/DateWithFormatter.test.ts    | 23 +-------
 .../time-format/utils/stringifyTimeInput.test.ts   | 23 ++++++++
 .../src/transformProps.ts                          |  2 +-
 .../src/utils/DateWithFormatter.ts                 | 62 ----------------------
 .../src/utils/formatValue.ts                       |  2 +-
 .../src/utils/useColDefs.ts                        |  7 ++-
 .../test/utils/formatDurationValue.test.ts}        | 23 +-------
 .../src/react-pivottable/TableRenderers.tsx        | 34 ++++++------
 .../test/react-pivottable/tableRenders.test.tsx    | 39 ++++++++++++++
 .../plugins/plugin-chart-table/src/TableChart.tsx  |  2 +-
 .../plugin-chart-table/src/transformProps.ts       |  2 +-
 .../plugin-chart-table/src/utils/formatValue.ts    |  2 +-
 .../plugin-chart-table/test/TableChart.test.tsx    |  2 +-
 .../test/utils/formatDurationValue.test.ts}        | 23 +-------
 17 files changed, 118 insertions(+), 157 deletions(-)

diff --git 
a/superset-frontend/plugins/plugin-chart-table/src/utils/DateWithFormatter.ts 
b/superset-frontend/packages/superset-ui-core/src/time-format/DateWithFormatter.ts
similarity index 82%
rename from 
superset-frontend/plugins/plugin-chart-table/src/utils/DateWithFormatter.ts
rename to 
superset-frontend/packages/superset-ui-core/src/time-format/DateWithFormatter.ts
index c167bc7fa3c..da818e88d4a 100644
--- 
a/superset-frontend/plugins/plugin-chart-table/src/utils/DateWithFormatter.ts
+++ 
b/superset-frontend/packages/superset-ui-core/src/time-format/DateWithFormatter.ts
@@ -16,15 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import {
-  DataRecordValue,
-  normalizeTimestamp,
-  TimeFormatFunction,
-} from '@superset-ui/core';
+import type { DataRecordValue } from '../query/types/QueryResponse';
+import type { TimeFormatFunction } from './types';
+import normalizeTimestamp from './utils/normalizeTimestamp';
 
 /**
  * Extended Date object with a custom formatter, and retains the original input
  * when the formatter is simple `String(..)`.
+ *
+ * `toString()` never formats an Invalid Date: it returns the original input
+ * instead. `stringifyTimeInput` relies on that when it falls back to
+ * `${value}` for an unparseable input, otherwise the two would call each other
+ * forever.
  */
 export default class DateWithFormatter extends Date {
   formatter: TimeFormatFunction;
diff --git 
a/superset-frontend/packages/superset-ui-core/src/time-format/index.ts 
b/superset-frontend/packages/superset-ui-core/src/time-format/index.ts
index e6023a02b59..2fa1ded9dd5 100644
--- a/superset-frontend/packages/superset-ui-core/src/time-format/index.ts
+++ b/superset-frontend/packages/superset-ui-core/src/time-format/index.ts
@@ -19,6 +19,7 @@
 
 export { default as TimeFormats, LOCAL_PREFIX } from './TimeFormats';
 export { default as TimeFormatter, PREVIEW_TIME } from './TimeFormatter';
+export { default as DateWithFormatter } from './DateWithFormatter';
 export { DEFAULT_D3_TIME_FORMAT } from './D3FormatConfig';
 
 export {
diff --git 
a/superset-frontend/packages/superset-ui-core/src/time-format/utils/stringifyTimeInput.ts
 
b/superset-frontend/packages/superset-ui-core/src/time-format/utils/stringifyTimeInput.ts
index 0c5f39e2a5f..1573d193ab6 100644
--- 
a/superset-frontend/packages/superset-ui-core/src/time-format/utils/stringifyTimeInput.ts
+++ 
b/superset-frontend/packages/superset-ui-core/src/time-format/utils/stringifyTimeInput.ts
@@ -28,8 +28,17 @@ export default function stringifyTimeInput(
   let time: Date;
   if (typeof value === 'string') {
     const trimmed = value.trim();
+    // A bare four-digit string is the ISO 8601 year-only form ("2017"), which
+    // every engine parses as January 1st of that year. Any other integer
+    // string is an epoch timestamp in milliseconds that was stringified on
+    // its way here, e.g. by the pivot table, and is not a valid Date input.
+    const isYear = /^\d{4}$/.test(trimmed);
     const isIntegerString = /^-?\d+$/.test(trimmed);
-    time = new Date(isIntegerString ? Number(trimmed) : value);
+    if (isYear) {
+      time = new Date(trimmed);
+    } else {
+      time = new Date(isIntegerString ? Number(trimmed) : value);
+    }
   } else {
     time = value instanceof Date ? value : new Date(value);
   }
@@ -37,7 +46,9 @@ export default function stringifyTimeInput(
   // An input that does not resolve to a valid date - a duration such as
   // "00:01:54", for instance - would otherwise be formatted from an Invalid
   // Date and render as "NaN:NaN:NaN". Fall back to its own representation,
-  // as is already done for null and undefined above.
+  // as is already done for null and undefined above. For a `DateWithFormatter`
+  // this calls its `toString()`, which returns the original input rather than
+  // re-entering the formatter; that guard is what keeps the fallback finite.
   if (Number.isNaN(time.getTime())) {
     return `${value}`;
   }
diff --git 
a/superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/DateWithFormatter.test.ts
 
b/superset-frontend/packages/superset-ui-core/test/time-format/DateWithFormatter.test.ts
similarity index 65%
copy from 
superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/DateWithFormatter.test.ts
copy to 
superset-frontend/packages/superset-ui-core/test/time-format/DateWithFormatter.test.ts
index a0bc415dc5b..a5d7243a013 100644
--- 
a/superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/DateWithFormatter.test.ts
+++ 
b/superset-frontend/packages/superset-ui-core/test/time-format/DateWithFormatter.test.ts
@@ -1,4 +1,4 @@
-/*
+/**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -16,11 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { getTimeFormatter } from '@superset-ui/core';
-import { GenericDataType } from '@apache-superset/core/common';
-import DateWithFormatter from '../../src/utils/DateWithFormatter';
-import { formatColumnValue } from '../../src/utils/formatValue';
-import { DataColumnMeta } from '../../src/types';
+import { DateWithFormatter, getTimeFormatter } from '@superset-ui/core';
 
 const formatter = getTimeFormatter('%H:%M:%S');
 
@@ -43,18 +39,3 @@ test('retains the original input when the formatter is 
String', () => {
   const value = new DateWithFormatter('00:01:54');
   expect(String(value)).toBe('00:01:54');
 });
-
-test('renders a duration cell through the column formatter without producing 
NaN', () => {
-  // The cell text is produced by formatColumnValue, which hands the wrapped
-  // value straight to the formatter rather than going through toString().
-  const column: DataColumnMeta = {
-    key: 'call_period',
-    label: 'call_period',
-    dataType: GenericDataType.Temporal,
-    formatter,
-    isNumeric: false,
-  };
-  const value = new DateWithFormatter('00:01:54', { formatter });
-
-  expect(formatColumnValue(column, value)).toEqual([false, '00:01:54']);
-});
diff --git 
a/superset-frontend/packages/superset-ui-core/test/time-format/utils/stringifyTimeInput.test.ts
 
b/superset-frontend/packages/superset-ui-core/test/time-format/utils/stringifyTimeInput.test.ts
index 8e79eaa6885..9fd9b8d68ce 100644
--- 
a/superset-frontend/packages/superset-ui-core/test/time-format/utils/stringifyTimeInput.test.ts
+++ 
b/superset-frontend/packages/superset-ui-core/test/time-format/utils/stringifyTimeInput.test.ts
@@ -16,6 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { DateWithFormatter, getTimeFormatter } from '@superset-ui/core';
 import stringifyTimeInput from 
'../../../src/time-format/utils/stringifyTimeInput';
 
 const format = (time: Date) => time.toISOString();
@@ -56,3 +57,25 @@ test('returns unparseable strings unchanged instead of 
formatting an Invalid Dat
 test('returns the representation of a Date that could not be resolved', () => {
   expect(stringifyTimeInput(new Date('00:01:54'), format)).toBe('Invalid 
Date');
 });
+
+test('treats a four-digit integer string as a year, not as milliseconds', () 
=> {
+  // "2017" is the ISO 8601 year-only form. Reading it as an epoch offset
+  // would silently turn it into two seconds past 1970.
+  expect(stringifyTimeInput('2017', format)).toBe('2017-01-01T00:00:00.000Z');
+  expect(stringifyTimeInput(' 1987 ', 
format)).toBe('1987-01-01T00:00:00.000Z');
+  // Longer digit strings stay epoch milliseconds.
+  expect(stringifyTimeInput('1704067200000', format)).toBe(
+    '2024-01-01T00:00:00.000Z',
+  );
+});
+
+test('returns the original input of an unparseable DateWithFormatter without 
re-entering the formatter', () => {
+  // The `${value}` fallback calls `DateWithFormatter.toString()`, which must
+  // return the input rather than call the formatter again, or the two would
+  // recurse until the stack overflows.
+  const formatter = getTimeFormatter('%H:%M:%S');
+  const value = new DateWithFormatter('00:01:54', { formatter });
+
+  expect(stringifyTimeInput(value, time => formatter(time))).toBe('00:01:54');
+  expect(formatter(value)).toBe('00:01:54');
+});
diff --git 
a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/transformProps.ts 
b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/transformProps.ts
index bc5752f1f49..49557462177 100644
--- a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/transformProps.ts
@@ -36,6 +36,7 @@ import {
   TimeFormatter,
   AgGridChartState,
   AgGridFilterModel,
+  DateWithFormatter,
 } from '@superset-ui/core';
 import { GenericDataType } from '@apache-superset/core/common';
 import { isEmpty, isEqual, merge } from 'lodash-es';
@@ -45,7 +46,6 @@ import {
   ColorSchemeEnum,
 } from '@superset-ui/chart-controls';
 import isEqualColumns from './utils/isEqualColumns';
-import DateWithFormatter from './utils/DateWithFormatter';
 import { BASIC_COLOR_FORMATTERS_ROW_KEY } from './consts';
 import {
   DataColumnMeta,
diff --git 
a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/DateWithFormatter.ts
 
b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/DateWithFormatter.ts
deleted file mode 100644
index c167bc7fa3c..00000000000
--- 
a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/DateWithFormatter.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "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.
- */
-import {
-  DataRecordValue,
-  normalizeTimestamp,
-  TimeFormatFunction,
-} from '@superset-ui/core';
-
-/**
- * Extended Date object with a custom formatter, and retains the original input
- * when the formatter is simple `String(..)`.
- */
-export default class DateWithFormatter extends Date {
-  formatter: TimeFormatFunction;
-
-  input: DataRecordValue;
-
-  constructor(
-    input: DataRecordValue,
-    { formatter = String }: { formatter?: TimeFormatFunction } = {},
-  ) {
-    let value = input;
-    // assuming timestamps without a timezone is in UTC time
-    if (typeof value === 'string') {
-      value = normalizeTimestamp(value);
-    }
-
-    super(value as string);
-
-    this.input = input;
-    this.formatter = formatter;
-    this.toString = (): string => {
-      if (this.formatter === String) {
-        return String(this.input);
-      }
-      // Values that are not parseable timestamps - durations such as
-      // "00:01:54" or "0 days 00:01:54", for instance - produce an Invalid
-      // Date, and formatting one renders as "NaN:NaN:NaN". Fall back to the
-      // original value instead.
-      if (Number.isNaN(this.getTime())) {
-        return String(this.input);
-      }
-      return this.formatter ? this.formatter(this) : Date.toString.call(this);
-    };
-  }
-}
diff --git 
a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/formatValue.ts 
b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/formatValue.ts
index 38585550c45..a85d279ae7c 100644
--- 
a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/formatValue.ts
+++ 
b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/formatValue.ts
@@ -22,6 +22,7 @@ import {
   isDefined,
   isProbablyHTML,
   sanitizeHtml,
+  DateWithFormatter,
 } from '@superset-ui/core';
 import { GenericDataType } from '@apache-superset/core/common';
 import {
@@ -29,7 +30,6 @@ import {
   ValueGetterParams,
 } from '@superset-ui/core/components/ThemedAgGridReact';
 import { DataColumnMeta, InputColumn } from '../types';
-import DateWithFormatter from './DateWithFormatter';
 
 /**
  * Format text for cell value.
diff --git 
a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/useColDefs.ts 
b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/useColDefs.ts
index c449274d210..54d999c8a47 100644
--- 
a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/useColDefs.ts
+++ 
b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/utils/useColDefs.ts
@@ -25,7 +25,11 @@ import {
   CellClassParams,
 } from '@superset-ui/core/components/ThemedAgGridReact';
 import { useCallback, useMemo } from 'react';
-import { DataRecordValue, JsonObject } from '@superset-ui/core';
+import {
+  DataRecordValue,
+  DateWithFormatter,
+  JsonObject,
+} from '@superset-ui/core';
 import { GenericDataType } from '@apache-superset/core/common';
 import { useTheme } from '@apache-superset/core/theme';
 import { ColorFormatters } from '@superset-ui/chart-controls';
@@ -42,7 +46,6 @@ import htmlTextFilterValueGetter, {
   htmlTextComparator,
 } from './htmlTextFilterValueGetter';
 import dateFilterComparator from './dateFilterComparator';
-import DateWithFormatter from './DateWithFormatter';
 import { getAggFunc } from './getAggFunc';
 import { TextCellRenderer } from '../renderers/TextCellRenderer';
 import { NumericCellRenderer } from '../renderers/NumericCellRenderer';
diff --git 
a/superset-frontend/plugins/plugin-chart-table/test/utils/DateWithFormatter.test.ts
 
b/superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/formatDurationValue.test.ts
similarity index 63%
rename from 
superset-frontend/plugins/plugin-chart-table/test/utils/DateWithFormatter.test.ts
rename to 
superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/formatDurationValue.test.ts
index a0bc415dc5b..d6024328bd5 100644
--- 
a/superset-frontend/plugins/plugin-chart-table/test/utils/DateWithFormatter.test.ts
+++ 
b/superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/formatDurationValue.test.ts
@@ -16,34 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { getTimeFormatter } from '@superset-ui/core';
+import { DateWithFormatter, getTimeFormatter } from '@superset-ui/core';
 import { GenericDataType } from '@apache-superset/core/common';
-import DateWithFormatter from '../../src/utils/DateWithFormatter';
 import { formatColumnValue } from '../../src/utils/formatValue';
 import { DataColumnMeta } from '../../src/types';
 
 const formatter = getTimeFormatter('%H:%M:%S');
 
-test('formats a parseable timestamp with the configured formatter', () => {
-  const value = new DateWithFormatter('2017-02-14T11:22:33Z', { formatter });
-  expect(String(value)).toBe('11:22:33');
-});
-
-test('renders the original value when it is not a parseable timestamp', () => {
-  // Duration columns hold values like these. They produce an Invalid Date,
-  // which used to be formatted and rendered as "NaN:NaN:NaN".
-  ['00:01:54', '0 days 00:01:54'].forEach(input => {
-    const value = new DateWithFormatter(input, { formatter });
-    expect(Number.isNaN(value.getTime())).toBe(true);
-    expect(String(value)).toBe(input);
-  });
-});
-
-test('retains the original input when the formatter is String', () => {
-  const value = new DateWithFormatter('00:01:54');
-  expect(String(value)).toBe('00:01:54');
-});
-
 test('renders a duration cell through the column formatter without producing 
NaN', () => {
   // The cell text is produced by formatColumnValue, which hands the wrapped
   // value straight to the formatter rather than going through toString().
diff --git 
a/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx
 
b/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx
index b5eed8f8c51..4b28b2a866f 100644
--- 
a/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx
+++ 
b/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx
@@ -44,6 +44,23 @@ import {
 import { PivotData, flatKey } from './utilities';
 import { Styles } from './Styles';
 
+/**
+ * Pivot keys are stringified on their way through `PivotData`, so a temporal
+ * header holding an epoch timestamp arrives as e.g. "1700000000000". Coerce
+ * such numeric strings back to numbers so temporal formatters (which expect
+ * an epoch) render correctly. A bare four-digit string is the ISO 8601
+ * year-only form ("2017"), which the shared `stringifyTimeInput` in core
+ * reads as that calendar year; coercing it would turn the year into two
+ * seconds past 1970, so it is passed through untouched.
+ */
+const toDateFormatterInput = (value: unknown): unknown =>
+  typeof value === 'string' &&
+  value.trim() !== '' &&
+  !/^\d{4}$/.test(value.trim()) &&
+  Number.isFinite(Number(value))
+    ? Number(value)
+    : value;
+
 type ClickCallback = (
   e: MouseEvent,
   value: unknown,
@@ -989,15 +1006,9 @@ export function TableRenderer(props: TableRendererProps) {
               />
             );
           };
-          // Coerce numeric timestamp strings to numbers so temporal formatters
-          // (which typically expect an epoch) render correctly.
           const rawHeaderCellValue = colKey[attrIdx];
           const headerCellFormatterValue =
-            typeof rawHeaderCellValue === 'string' &&
-            rawHeaderCellValue.trim() !== '' &&
-            Number.isFinite(Number(rawHeaderCellValue))
-              ? Number(rawHeaderCellValue)
-              : rawHeaderCellValue;
+            toDateFormatterInput(rawHeaderCellValue);
           const headerCellFormattedValue =
             dateFormatters?.[attrName]?.(headerCellFormatterValue) ??
             rawHeaderCellValue;
@@ -1263,14 +1274,7 @@ export function TableRenderer(props: TableRendererProps) 
{
             ? toggleRowKey(flatRowKeySlice)
             : null;
 
-          // Coerce numeric timestamp strings to numbers so temporal formatters
-          // (which typically expect an epoch) render correctly.
-          const headerFormatterValue =
-            typeof r === 'string' &&
-            r.trim() !== '' &&
-            Number.isFinite(Number(r))
-              ? Number(r)
-              : r;
+          const headerFormatterValue = toDateFormatterInput(r);
           const headerCellFormattedValue =
             dateFormatters?.[settingsRowAttrs[i]]?.(headerFormatterValue) ?? r;
           const isActiveHeader = valueCellClassName.includes('active');
diff --git 
a/superset-frontend/plugins/plugin-chart-pivot-table/test/react-pivottable/tableRenders.test.tsx
 
b/superset-frontend/plugins/plugin-chart-pivot-table/test/react-pivottable/tableRenders.test.tsx
index d1b7c6e9590..dca6c3928ff 100644
--- 
a/superset-frontend/plugins/plugin-chart-pivot-table/test/react-pivottable/tableRenders.test.tsx
+++ 
b/superset-frontend/plugins/plugin-chart-pivot-table/test/react-pivottable/tableRenders.test.tsx
@@ -21,6 +21,7 @@ import type { ReactElement } from 'react';
 import '@testing-library/jest-dom';
 import { fireEvent, render, screen } from '@testing-library/react';
 import { supersetTheme, ThemeProvider } from '@apache-superset/core/theme';
+import { getTimeFormatter } from '@superset-ui/core';
 import { TableRenderer } from '../../src/react-pivottable/TableRenderers';
 import {
   aggregatorTemplates,
@@ -689,6 +690,44 @@ test('TableRenderer coerces numeric timestamp strings to 
numbers for row header
   expect(screen.getByText('row:red')).toBeInTheDocument();
 });
 
+test('TableRenderer passes four-digit year strings through to column header 
date formatters uncoerced', () => {
+  const data = [
+    { shape: '2017', color: 'blue', value: 1 },
+    { shape: '1700000000000', color: 'blue', value: 2 },
+  ];
+  const props = buildDefaultProps({
+    data,
+    rows: ['color'],
+    cols: ['shape'],
+    tableOptions: { dateFormatters: { shape: getTimeFormatter('%Y') } },
+  });
+  renderWithTheme(<TableRenderer {...props} />);
+
+  // "2017" is the ISO year-only form and must render as that year, not as
+  // 2.017 seconds past the epoch; a stringified epoch still coerces.
+  expect(screen.getByText('2017')).toBeInTheDocument();
+  expect(screen.getByText('2023')).toBeInTheDocument();
+  expect(screen.queryByText('1970')).not.toBeInTheDocument();
+});
+
+test('TableRenderer passes four-digit year strings through to row header date 
formatters uncoerced', () => {
+  const data = [
+    { color: '2017', shape: 'circle', value: 1 },
+    { color: '1700000000000', shape: 'circle', value: 2 },
+  ];
+  const props = buildDefaultProps({
+    data,
+    rows: ['color'],
+    cols: ['shape'],
+    tableOptions: { dateFormatters: { color: getTimeFormatter('%Y') } },
+  });
+  renderWithTheme(<TableRenderer {...props} />);
+
+  expect(screen.getByText('2017')).toBeInTheDocument();
+  expect(screen.getByText('2023')).toBeInTheDocument();
+  expect(screen.queryByText('1970')).not.toBeInTheDocument();
+});
+
 test('TableRenderer applies cellColorFormatters background and contrast color 
to column headers', () => {
   const cellColorFormatters = {
     shape: [
diff --git a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx 
b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
index e048b95b91f..b3939d65485 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
@@ -47,6 +47,7 @@ import {
   extractTextFromHTML,
   TimeGranularity,
   forceHexAlpha,
+  DateWithFormatter,
 } from '@superset-ui/core';
 import {
   styled,
@@ -98,7 +99,6 @@ import { formatColumnValue } from './utils/formatValue';
 import { PAGE_SIZE_OPTIONS, SERVER_PAGE_SIZE_OPTIONS } from './consts';
 import { updateTableOwnState } from './DataTable/utils/externalAPIs';
 import getScrollBarSize from './DataTable/utils/getScrollBarSize';
-import DateWithFormatter from './utils/DateWithFormatter';
 
 type ValueRange = [number, number];
 
diff --git a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts 
b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
index 1c22204346c..21442b9f0fa 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
@@ -36,6 +36,7 @@ import {
   SMART_DATE_ID,
   TimeFormats,
   TimeFormatter,
+  DateWithFormatter,
 } from '@superset-ui/core';
 import { GenericDataType } from '@apache-superset/core/common';
 import {
@@ -47,7 +48,6 @@ import {
 
 import { isEmpty, merge } from 'lodash-es';
 import isEqualColumns from './utils/isEqualColumns';
-import DateWithFormatter from './utils/DateWithFormatter';
 import {
   BasicColorFormatterType,
   DataColumnMeta,
diff --git 
a/superset-frontend/plugins/plugin-chart-table/src/utils/formatValue.ts 
b/superset-frontend/plugins/plugin-chart-table/src/utils/formatValue.ts
index e5799f621ab..1a1c15619f1 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/utils/formatValue.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/utils/formatValue.ts
@@ -22,10 +22,10 @@ import {
   getSmallNumberFormatter,
   isProbablyHTML,
   sanitizeHtml,
+  DateWithFormatter,
 } from '@superset-ui/core';
 import { GenericDataType } from '@apache-superset/core/common';
 import { DataColumnMeta } from '../types';
-import DateWithFormatter from './DateWithFormatter';
 
 /**
  * Format text for cell value.
diff --git 
a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx 
b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
index a8e27a03644..a89247035e1 100644
--- a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
@@ -40,13 +40,13 @@ import {
   TimeGranularity,
   SMART_DATE_ID,
   getTimeFormatterForGranularity,
+  DateWithFormatter,
 } from '@superset-ui/core';
 import { CellProps, Column, HeaderProps } from 'react-table';
 import DataTable from '../src/DataTable/DataTable';
 import TableChart, { sanitizeHeaderId } from '../src/TableChart';
 import { GenericDataType } from '@apache-superset/core/common';
 import transformProps from '../src/transformProps';
-import DateWithFormatter from '../src/utils/DateWithFormatter';
 import testData from './testData';
 import { ProviderWrapper } from './testHelpers';
 
diff --git 
a/superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/DateWithFormatter.test.ts
 
b/superset-frontend/plugins/plugin-chart-table/test/utils/formatDurationValue.test.ts
similarity index 63%
rename from 
superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/DateWithFormatter.test.ts
rename to 
superset-frontend/plugins/plugin-chart-table/test/utils/formatDurationValue.test.ts
index a0bc415dc5b..d6024328bd5 100644
--- 
a/superset-frontend/plugins/plugin-chart-ag-grid-table/test/utils/DateWithFormatter.test.ts
+++ 
b/superset-frontend/plugins/plugin-chart-table/test/utils/formatDurationValue.test.ts
@@ -16,34 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { getTimeFormatter } from '@superset-ui/core';
+import { DateWithFormatter, getTimeFormatter } from '@superset-ui/core';
 import { GenericDataType } from '@apache-superset/core/common';
-import DateWithFormatter from '../../src/utils/DateWithFormatter';
 import { formatColumnValue } from '../../src/utils/formatValue';
 import { DataColumnMeta } from '../../src/types';
 
 const formatter = getTimeFormatter('%H:%M:%S');
 
-test('formats a parseable timestamp with the configured formatter', () => {
-  const value = new DateWithFormatter('2017-02-14T11:22:33Z', { formatter });
-  expect(String(value)).toBe('11:22:33');
-});
-
-test('renders the original value when it is not a parseable timestamp', () => {
-  // Duration columns hold values like these. They produce an Invalid Date,
-  // which used to be formatted and rendered as "NaN:NaN:NaN".
-  ['00:01:54', '0 days 00:01:54'].forEach(input => {
-    const value = new DateWithFormatter(input, { formatter });
-    expect(Number.isNaN(value.getTime())).toBe(true);
-    expect(String(value)).toBe(input);
-  });
-});
-
-test('retains the original input when the formatter is String', () => {
-  const value = new DateWithFormatter('00:01:54');
-  expect(String(value)).toBe('00:01:54');
-});
-
 test('renders a duration cell through the column formatter without producing 
NaN', () => {
   // The cell text is produced by formatColumnValue, which hands the wrapped
   // value straight to the formatter rather than going through toString().

Reply via email to