This is an automated email from the ASF dual-hosted git repository.
beto 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 f0080f9c55 fix: smarter date formatter (#25404)
f0080f9c55 is described below
commit f0080f9c559c407c5d06e03db27f2cc40fb227e2
Author: Beto Dealmeida <[email protected]>
AuthorDate: Wed Sep 27 09:52:36 2023 -0700
fix: smarter date formatter (#25404)
---
superset-frontend/jest.config.js | 3 +
.../formatters/finestTemporalGrain.test.ts | 63 +++++++++++++++++
.../time-format/formatters/finestTemporalGrain.ts | 80 ++++++++++++++++++++++
.../superset-ui-core/src/time-format/index.ts | 1 +
.../components/Select/SelectFilterPlugin.tsx | 6 +-
5 files changed, 150 insertions(+), 3 deletions(-)
diff --git a/superset-frontend/jest.config.js b/superset-frontend/jest.config.js
index 24e4886ecd..316102c5c2 100644
--- a/superset-frontend/jest.config.js
+++ b/superset-frontend/jest.config.js
@@ -17,6 +17,9 @@
* under the License.
*/
+// timezone for unit tests
+process.env.TZ = 'America/New_York';
+
module.exports = {
testRegex:
'\\/superset-frontend\\/(spec|src|plugins|packages|tools)\\/.*(_spec|\\.test)\\.[jt]sx?$',
diff --git
a/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.test.ts
b/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.test.ts
new file mode 100644
index 0000000000..6e4f07df4b
--- /dev/null
+++
b/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.test.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 finestTemporalGrain from './finestTemporalGrain';
+
+test('finestTemporalGrain', () => {
+ const monthFormatter = finestTemporalGrain([
+ new Date('2003-01-01 00:00:00Z').getTime(),
+ new Date('2003-02-01 00:00:00Z').getTime(),
+ ]);
+ expect(monthFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe(
+ '2003-01-01',
+ );
+ expect(monthFormatter(new Date('2003-02-01 00:00:00Z').getTime())).toBe(
+ '2003-02-01',
+ );
+
+ const yearFormatter = finestTemporalGrain([
+ new Date('2003-01-01 00:00:00Z').getTime(),
+ new Date('2004-01-01 00:00:00Z').getTime(),
+ ]);
+ expect(yearFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe(
+ '2003',
+ );
+ expect(yearFormatter(new Date('2004-01-01 00:00:00Z').getTime())).toBe(
+ '2004',
+ );
+
+ const milliSecondFormatter = finestTemporalGrain([
+ new Date('2003-01-01 00:00:00Z').getTime(),
+ new Date('2003-04-05 06:07:08.123Z').getTime(),
+ ]);
+ expect(milliSecondFormatter(new Date('2003-01-01
00:00:00Z').getTime())).toBe(
+ '2003-01-01 00:00:00.000',
+ );
+
+ const localTimeFormatter = finestTemporalGrain(
+ [
+ new Date('2003-01-01 00:00:00Z').getTime(),
+ new Date('2003-02-01 00:00:00Z').getTime(),
+ ],
+ true,
+ );
+ expect(localTimeFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe(
+ '2002-12-31 19:00',
+ );
+});
diff --git
a/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.ts
b/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.ts
new file mode 100644
index 0000000000..c03b7ec159
--- /dev/null
+++
b/superset-frontend/packages/superset-ui-core/src/time-format/formatters/finestTemporalGrain.ts
@@ -0,0 +1,80 @@
+/*
+ * 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 { utcFormat, timeFormat } from 'd3-time-format';
+import { utcUtils, localTimeUtils } from '../utils/d3Time';
+import TimeFormatter from '../TimeFormatter';
+
+/*
+ * A formatter that examines all the values, and uses the finest temporal
grain.
+ */
+export default function finestTemporalGrain(
+ values: any[],
+ useLocalTime = false,
+) {
+ const format = useLocalTime ? timeFormat : utcFormat;
+
+ const formatMillisecond = format('%Y-%m-%d %H:%M:%S.%L');
+ const formatSecond = format('%Y-%m-%d %H:%M:%S');
+ const formatMinute = format('%Y-%m-%d %H:%M');
+ const formatHour = format('%Y-%m-%d %H:%M');
+ const formatDay = format('%Y-%m-%d');
+ const formatMonth = format('%Y-%m-%d');
+ const formatYear = format('%Y');
+
+ const {
+ hasMillisecond,
+ hasSecond,
+ hasMinute,
+ hasHour,
+ isNotFirstDayOfMonth,
+ isNotFirstMonth,
+ } = useLocalTime ? localTimeUtils : utcUtils;
+
+ let formatFunc = formatYear;
+ values.forEach((value: any) => {
+ if (formatFunc === formatYear && isNotFirstMonth(value)) {
+ formatFunc = formatMonth;
+ }
+ if (formatFunc === formatMonth && isNotFirstDayOfMonth(value)) {
+ formatFunc = formatDay;
+ }
+ if (formatFunc === formatDay && hasHour(value)) {
+ formatFunc = formatHour;
+ }
+ if (formatFunc === formatHour && hasMinute(value)) {
+ formatFunc = formatMinute;
+ }
+ if (formatFunc === formatMinute && hasSecond(value)) {
+ formatFunc = formatSecond;
+ }
+ if (formatFunc === formatSecond && hasMillisecond(value)) {
+ formatFunc = formatMillisecond;
+ }
+ });
+
+ return new TimeFormatter({
+ description:
+ 'Use the finest grain in an array of dates to format all dates in the
array',
+ formatFunc,
+ id: 'finest_temporal_grain',
+ label: 'Format temporal columns with the finest grain',
+ useLocalTime,
+ });
+}
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 53f23f3643..b0d95c1433 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
@@ -35,6 +35,7 @@ export { default as createMultiFormatter } from
'./factories/createMultiFormatte
export { default as smartDateFormatter } from './formatters/smartDate';
export { default as smartDateDetailedFormatter } from
'./formatters/smartDateDetailed';
export { default as smartDateVerboseFormatter } from
'./formatters/smartDateVerbose';
+export { default as finestTemporalGrainFormatter } from
'./formatters/finestTemporalGrain';
export { default as normalizeTimestamp } from './utils/normalizeTimestamp';
export { default as denormalizeTimestamp } from './utils/denormalizeTimestamp';
diff --git
a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
index 2c5d919188..bef70e68f3 100644
--- a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
+++ b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx
@@ -26,7 +26,7 @@ import {
GenericDataType,
getColumnLabel,
JsonObject,
- smartDateDetailedFormatter,
+ finestTemporalGrainFormatter,
t,
tn,
} from '@superset-ui/core';
@@ -117,9 +117,9 @@ export default function PluginFilterSelect(props:
PluginFilterSelectProps) {
const labelFormatter = useMemo(
() =>
getDataRecordFormatter({
- timeFormatter: smartDateDetailedFormatter,
+ timeFormatter: finestTemporalGrainFormatter(data.map(el => el.col)),
}),
- [],
+ [data],
);
const updateDataMask = useCallback(