Skip to content

Commit f0080f9

Browse files
fix: smarter date formatter (apache#25404)
1 parent 463962a commit f0080f9

File tree

5 files changed

+150
-3
lines changed

5 files changed

+150
-3
lines changed

superset-frontend/jest.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
* under the License.
1818
*/
1919

20+
// timezone for unit tests
21+
process.env.TZ = 'America/New_York';
22+
2023
module.exports = {
2124
testRegex:
2225
'\\/superset-frontend\\/(spec|src|plugins|packages|tools)\\/.*(_spec|\\.test)\\.[jt]sx?$',
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0,
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import finestTemporalGrain from './finestTemporalGrain';
21+
22+
test('finestTemporalGrain', () => {
23+
const monthFormatter = finestTemporalGrain([
24+
new Date('2003-01-01 00:00:00Z').getTime(),
25+
new Date('2003-02-01 00:00:00Z').getTime(),
26+
]);
27+
expect(monthFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe(
28+
'2003-01-01',
29+
);
30+
expect(monthFormatter(new Date('2003-02-01 00:00:00Z').getTime())).toBe(
31+
'2003-02-01',
32+
);
33+
34+
const yearFormatter = finestTemporalGrain([
35+
new Date('2003-01-01 00:00:00Z').getTime(),
36+
new Date('2004-01-01 00:00:00Z').getTime(),
37+
]);
38+
expect(yearFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe(
39+
'2003',
40+
);
41+
expect(yearFormatter(new Date('2004-01-01 00:00:00Z').getTime())).toBe(
42+
'2004',
43+
);
44+
45+
const milliSecondFormatter = finestTemporalGrain([
46+
new Date('2003-01-01 00:00:00Z').getTime(),
47+
new Date('2003-04-05 06:07:08.123Z').getTime(),
48+
]);
49+
expect(milliSecondFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe(
50+
'2003-01-01 00:00:00.000',
51+
);
52+
53+
const localTimeFormatter = finestTemporalGrain(
54+
[
55+
new Date('2003-01-01 00:00:00Z').getTime(),
56+
new Date('2003-02-01 00:00:00Z').getTime(),
57+
],
58+
true,
59+
);
60+
expect(localTimeFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe(
61+
'2002-12-31 19:00',
62+
);
63+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { utcFormat, timeFormat } from 'd3-time-format';
21+
import { utcUtils, localTimeUtils } from '../utils/d3Time';
22+
import TimeFormatter from '../TimeFormatter';
23+
24+
/*
25+
* A formatter that examines all the values, and uses the finest temporal grain.
26+
*/
27+
export default function finestTemporalGrain(
28+
values: any[],
29+
useLocalTime = false,
30+
) {
31+
const format = useLocalTime ? timeFormat : utcFormat;
32+
33+
const formatMillisecond = format('%Y-%m-%d %H:%M:%S.%L');
34+
const formatSecond = format('%Y-%m-%d %H:%M:%S');
35+
const formatMinute = format('%Y-%m-%d %H:%M');
36+
const formatHour = format('%Y-%m-%d %H:%M');
37+
const formatDay = format('%Y-%m-%d');
38+
const formatMonth = format('%Y-%m-%d');
39+
const formatYear = format('%Y');
40+
41+
const {
42+
hasMillisecond,
43+
hasSecond,
44+
hasMinute,
45+
hasHour,
46+
isNotFirstDayOfMonth,
47+
isNotFirstMonth,
48+
} = useLocalTime ? localTimeUtils : utcUtils;
49+
50+
let formatFunc = formatYear;
51+
values.forEach((value: any) => {
52+
if (formatFunc === formatYear && isNotFirstMonth(value)) {
53+
formatFunc = formatMonth;
54+
}
55+
if (formatFunc === formatMonth && isNotFirstDayOfMonth(value)) {
56+
formatFunc = formatDay;
57+
}
58+
if (formatFunc === formatDay && hasHour(value)) {
59+
formatFunc = formatHour;
60+
}
61+
if (formatFunc === formatHour && hasMinute(value)) {
62+
formatFunc = formatMinute;
63+
}
64+
if (formatFunc === formatMinute && hasSecond(value)) {
65+
formatFunc = formatSecond;
66+
}
67+
if (formatFunc === formatSecond && hasMillisecond(value)) {
68+
formatFunc = formatMillisecond;
69+
}
70+
});
71+
72+
return new TimeFormatter({
73+
description:
74+
'Use the finest grain in an array of dates to format all dates in the array',
75+
formatFunc,
76+
id: 'finest_temporal_grain',
77+
label: 'Format temporal columns with the finest grain',
78+
useLocalTime,
79+
});
80+
}

superset-frontend/packages/superset-ui-core/src/time-format/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export { default as createMultiFormatter } from './factories/createMultiFormatte
3535
export { default as smartDateFormatter } from './formatters/smartDate';
3636
export { default as smartDateDetailedFormatter } from './formatters/smartDateDetailed';
3737
export { default as smartDateVerboseFormatter } from './formatters/smartDateVerbose';
38+
export { default as finestTemporalGrainFormatter } from './formatters/finestTemporalGrain';
3839

3940
export { default as normalizeTimestamp } from './utils/normalizeTimestamp';
4041
export { default as denormalizeTimestamp } from './utils/denormalizeTimestamp';

superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
GenericDataType,
2727
getColumnLabel,
2828
JsonObject,
29-
smartDateDetailedFormatter,
29+
finestTemporalGrainFormatter,
3030
t,
3131
tn,
3232
} from '@superset-ui/core';
@@ -117,9 +117,9 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
117117
const labelFormatter = useMemo(
118118
() =>
119119
getDataRecordFormatter({
120-
timeFormatter: smartDateDetailedFormatter,
120+
timeFormatter: finestTemporalGrainFormatter(data.map(el => el.col)),
121121
}),
122-
[],
122+
[data],
123123
);
124124

125125
const updateDataMask = useCallback(

0 commit comments

Comments
 (0)