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().