Skip to content

Commit

Permalink
Add @superset-ui/time-format (#38)
Browse files Browse the repository at this point in the history
feat: Add @superset-ui/time-format

* update unit test (+6 squashed commits)
Squashed commits:
[5fae1ed] fix lint
[7672544] support local and utc
[c33fae7] update unit tests
[97fdc0d] fix all unit tests
[88e8029] add formatter code
[a33c76c] initialize

* update readme

* add formatDate and formatDateVerbose equivalent

* add unit test

* extract constants

* get to 100 coverage

* update readme

* Update preview test

* explicitly disable magic number
  • Loading branch information
kristw authored and zhaoyongjie committed Nov 24, 2021
1 parent 29eb325 commit b870159
Show file tree
Hide file tree
Showing 22 changed files with 782 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ applications that leverage a Superset backend :chart_with_upwards_trend:
| [@superset-ui/core](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-core) | [![Version](https://img.shields.io/npm/v/@superset-ui/core.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/core.svg?style=flat-square) |
| [@superset-ui/generator-superset](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-generator-superset) | [![Version](https://img.shields.io/npm/v/@superset-ui/generator-superset.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/generator-superset.svg?style=flat-square) |
| [@superset-ui/number-format](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-number-format) | [![Version](https://img.shields.io/npm/v/@superset-ui/number-format.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/number-format.svg?style=flat-square) |
| [@superset-ui/time-format](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-time-format) | [![Version](https://img.shields.io/npm/v/@superset-ui/time-format.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/time-format.svg?style=flat-square) |
| [@superset-ui/translation](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-translation) | [![Version](https://img.shields.io/npm/v/@superset-ui/translation.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/translation.svg?style=flat-square) |

#### Coming :soon:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## @superset-ui/time-format

[![Version](https://img.shields.io/npm/v/@superset-ui/time-format.svg?style=flat)](https://img.shields.io/npm/v/@superset-ui/time-format.svg?style=flat)
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui.svg?path=packages%2Fsuperset-ui-time-format&style=flat-square)](https://david-dm.org/apache-superset/superset-ui?path=packages/superset-ui-time-format)

Description

#### Example usage

Add `@superset-ui/time-format`, a module for formatting time. Functions `getTimeFormatter` and `formatTime` should be used instead of calling `d3.utcFormat` or `d3.timeFormat` directly.

```js
import { getTimeFormatter } from '@superset-ui/time-format';
const formatter = getTimeFormatter('%Y-%m-d');
console.log(formatter(new Date()));
```

or

```js
import { formatTime } from '@superset-ui/time-format';
console.log(formatTime('%Y-%m-d', new Date()));
```

It is powered by a registry to support registration of custom formatting, with fallback to `d3.utcFormat` or `d3.timeFormat` (if the formatId starts with `local!`)

```js
import { getTimeFormatterRegistry, formatTime, TimeFormatter } from '@superset-ui/time-format';

getTimeFormatterRegistry().registerValue('my_format', new TimeFormatter({
id: 'my_format',
formatFunc: v => `my special format of ${utcFormat('%Y')(v)}`
});

console.log(formatTime('my_format', new Date(2018)));
// prints 'my special format of 2018'
```
It also define constants for common d3 time formats. See [TimeFormats.js](https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-time-format/src/TimeFormats.js).
```js
import { TimeFormats } from '@superset-ui/time-format';

TimeFormats.DATABASE_DATETIME // '%Y-%m-%d %H:%M:%S'
TimeFormats.US_DATE // '%m/%d/%Y'
```
#### API
`fn(args)`
- Do something
### Development
`@data-ui/build-config` is used to manage the build configuration for this package including babel
builds, jest testing, eslint, and prettier.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@superset-ui/time-format",
"version": "0.0.0",
"description": "Superset UI time-format",
"sideEffects": false,
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui.git"
},
"keywords": [
"superset"
],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@superset-ui/core": "^0.6.0",
"d3-time": "^1.0.10",
"d3-time-format": "^2.1.3",
"lodash": "^4.17.11"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const LOCAL_PREFIX = 'local!';

export const DATABASE_DATETIME = '%Y-%m-%d %H:%M:%S';
export const DATABASE_DATETIME_REVERSE = '%d-%m-%Y %H:%M:%S';
export const US_DATE = '%m/%d/%Y';
export const INTERNATIONAL_DATE = '%d/%m/%Y';
export const DATABASE_DATE = '%Y-%m-%d';
export const TIME = '%H:%M:%S';
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* eslint-disable no-magic-numbers */

import { ExtensibleFunction, isRequired } from '@superset-ui/core';

export const PREVIEW_TIME = new Date(Date.UTC(2017, 1, 14, 11, 22, 33));

export default class TimeFormatter extends ExtensibleFunction {
constructor({
id = isRequired('config.id'),
label,
description = '',
formatFunc = isRequired('config.formatFunc'),
useLocalTime = false,
} = {}) {
super((...args) => this.format(...args));

this.id = id;
this.label = label || id;
this.description = description;
this.formatFunc = formatFunc;
this.useLocalTime = useLocalTime;
}

format(value) {
if (value === null || value === undefined) {
return value;
}

return this.formatFunc(value);
}

preview(value = PREVIEW_TIME) {
return `${value.toUTCString()} => ${this.format(value)}`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { RegistryWithDefaultKey } from '@superset-ui/core';
import { DATABASE_DATETIME, LOCAL_PREFIX } from './TimeFormats';
import createD3TimeFormatter from './factories/createD3TimeFormatter';

const DEFAULT_FORMAT = DATABASE_DATETIME;

export default class TimeFormatterRegistry extends RegistryWithDefaultKey {
constructor() {
super({
initialDefaultKey: DEFAULT_FORMAT,
name: 'TimeFormatter',
});
}

get(format) {
const targetFormat = format || this.defaultKey;

if (this.has(targetFormat)) {
return super.get(targetFormat);
}

// Create new formatter if does not exist
const useLocalTime = targetFormat.startsWith(LOCAL_PREFIX);
const formatString = targetFormat.replace(LOCAL_PREFIX, '');
const formatter = createD3TimeFormatter({ formatString, useLocalTime });
this.registerValue(targetFormat, formatter);

return formatter;
}

format(format, value) {
return this.get(format)(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { makeSingleton } from '@superset-ui/core';
import TimeFormatterRegistry from './TimeFormatterRegistry';

const getInstance = makeSingleton(TimeFormatterRegistry);

export default getInstance;

export function getTimeFormatter(formatId) {
return getInstance().get(formatId);
}

export function formatTime(formatId, value) {
return getInstance().format(formatId, value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { utcFormat, timeFormat } from 'd3-time-format';
import { isRequired } from '@superset-ui/core';
import TimeFormatter from '../TimeFormatter';
import { LOCAL_PREFIX } from '../TimeFormats';

export default function createD3TimeFormatter({
description,
formatString = isRequired('formatString'),
label,
useLocalTime = false,
}) {
const id = useLocalTime ? `${LOCAL_PREFIX}${formatString}` : formatString;
const format = useLocalTime ? timeFormat : utcFormat;
const formatFunc = format(formatString);

return new TimeFormatter({
description,
formatFunc,
id,
label,
useLocalTime,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { utcFormat, timeFormat } from 'd3-time-format';
import { utcUtils, localTimeUtils } from '../utils';
import TimeFormatter from '../TimeFormatter';

export default function createMultiFormatter({
id,
label,
description,
formats = {},
useLocalTime = false,
}) {
const {
millisecond = '.%L',
second = ':%S',
minute = '%I:%M',
hour = '%I %p',
day = '%a %d',
week = '%b %d',
month = '%B',
year = '%Y',
} = formats;

const format = useLocalTime ? timeFormat : utcFormat;

const formatMillisecond = format(millisecond);
const formatSecond = format(second);
const formatMinute = format(minute);
const formatHour = format(hour);
const formatDay = format(day);
const formatFirstDayOfWeek = format(week);
const formatMonth = format(month);
const formatYear = format(year);

const {
hasMillisecond,
hasSecond,
hasMinute,
hasHour,
isNotFirstDayOfMonth,
isNotFirstDayOfWeek,
isNotFirstMonth,
} = useLocalTime ? localTimeUtils : utcUtils;

function multiFormatFunc(date) {
if (hasMillisecond(date)) {
return formatMillisecond;
} else if (hasSecond(date)) {
return formatSecond;
} else if (hasMinute(date)) {
return formatMinute;
} else if (hasHour(date)) {
return formatHour;
} else if (isNotFirstDayOfMonth(date)) {
return isNotFirstDayOfWeek(date) ? formatDay : formatFirstDayOfWeek;
} else if (isNotFirstMonth(date)) {
return formatMonth;
}

return formatYear;
}

return new TimeFormatter({
description,
formatFunc: date => multiFormatFunc(date)(date),
id,
label,
useLocalTime,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* eslint-disable sort-keys */

import createMultiFormatter from '../factories/createMultiFormatter';

const smartDateFormatter = createMultiFormatter({
id: 'smart_date',
label: 'Adaptative Formatting',
formats: {
millisecond: '.%Lms',
second: ':%Ss',
minute: '%I:%M',
hour: '%I %p',
day: '%a %d',
week: '%b %d',
month: '%B',
year: '%Y',
},
});

export default smartDateFormatter;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* eslint-disable sort-keys */

import createMultiFormatter from '../factories/createMultiFormatter';

const smartDateFormatter = createMultiFormatter({
id: 'smart_date_verbose',
label: 'Verbose Adaptative Formatting',
formats: {
millisecond: '.%L',
second: ':%S',
minute: '%a %b %d, %I:%M %p',
hour: '%a %b %d, %I %p',
day: '%a %b %-e',
week: '%a %b %-e',
month: '%b %Y',
year: '%Y',
},
});

export default smartDateFormatter;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as TimeFormats from './TimeFormats';

export {
default as getTimeFormatterRegistry,
formatTime,
getTimeFormatter,
} from './TimeFormatterRegistrySingleton';

export { default as TimeFormatter, PREVIEW_TIME } from './TimeFormatter';
export { TimeFormats };
Loading

0 comments on commit b870159

Please sign in to comment.