mirror of
https://github.com/apache/superset.git
synced 2024-09-19 20:19:37 -04:00
feat(time-format): improve support for formatting with granularity in mind (#509)
* feat(time-format): add support for granularity * feat: create time range from granularity * fix: update format * wip * feat: refactor getFormatter * feat: reconcile api * test: add unit tests * refactor: clean up * refactor: createTime * refactor: improve end time computation to be daylight saving compatible
This commit is contained in:
parent
fadf0d61a2
commit
17075ae021
@ -0,0 +1,30 @@
|
|||||||
|
import TimeFormats from './TimeFormats';
|
||||||
|
import { TimeGranularity } from './types';
|
||||||
|
|
||||||
|
const { DATABASE_DATE, DATABASE_DATETIME } = TimeFormats;
|
||||||
|
const MINUTE = '%Y-%m-%d %H:%M';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map time granularity to d3-format string
|
||||||
|
*/
|
||||||
|
const TimeFormatsForGranularity: Record<TimeGranularity, string> = {
|
||||||
|
[TimeGranularity.DATE]: DATABASE_DATE,
|
||||||
|
[TimeGranularity.SECOND]: DATABASE_DATETIME,
|
||||||
|
[TimeGranularity.MINUTE]: MINUTE,
|
||||||
|
[TimeGranularity.FIVE_MINUTES]: MINUTE,
|
||||||
|
[TimeGranularity.TEN_MINUTES]: MINUTE,
|
||||||
|
[TimeGranularity.FIFTEEN_MINUTES]: MINUTE,
|
||||||
|
[TimeGranularity.HALF_HOUR]: MINUTE,
|
||||||
|
[TimeGranularity.HOUR]: '%Y-%m-%d %H:00',
|
||||||
|
[TimeGranularity.DAY]: DATABASE_DATE,
|
||||||
|
[TimeGranularity.WEEK]: DATABASE_DATE,
|
||||||
|
[TimeGranularity.MONTH]: '%b %Y',
|
||||||
|
[TimeGranularity.QUARTER]: '%Y Q%q',
|
||||||
|
[TimeGranularity.YEAR]: '%Y',
|
||||||
|
[TimeGranularity.WEEK_STARTING_SUNDAY]: DATABASE_DATE,
|
||||||
|
[TimeGranularity.WEEK_STARTING_MONDAY]: DATABASE_DATE,
|
||||||
|
[TimeGranularity.WEEK_ENDING_SATURDAY]: DATABASE_DATE,
|
||||||
|
[TimeGranularity.WEEK_ENDING_SUNDAY]: DATABASE_DATE,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TimeFormatsForGranularity;
|
@ -1,5 +1,6 @@
|
|||||||
import { ExtensibleFunction, isRequired } from '@superset-ui/core';
|
import { ExtensibleFunction, isRequired } from '@superset-ui/core';
|
||||||
import { TimeFormatFunction } from './types';
|
import { TimeFormatFunction } from './types';
|
||||||
|
import stringifyTimeInput from './utils/stringifyTimeInput';
|
||||||
|
|
||||||
export const PREVIEW_TIME = new Date(Date.UTC(2017, 1, 14, 11, 22, 33));
|
export const PREVIEW_TIME = new Date(Date.UTC(2017, 1, 14, 11, 22, 33));
|
||||||
|
|
||||||
@ -45,11 +46,7 @@ class TimeFormatter extends ExtensibleFunction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
format(value: Date | number | null | undefined) {
|
format(value: Date | number | null | undefined) {
|
||||||
if (value === null || value === undefined) {
|
return stringifyTimeInput(value, time => this.formatFunc(time));
|
||||||
return `${value}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.formatFunc(value instanceof Date ? value : new Date(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preview(value: Date = PREVIEW_TIME) {
|
preview(value: Date = PREVIEW_TIME) {
|
||||||
|
@ -1,14 +1,63 @@
|
|||||||
import { makeSingleton } from '@superset-ui/core';
|
import { makeSingleton } from '@superset-ui/core';
|
||||||
import TimeFormatterRegistry from './TimeFormatterRegistry';
|
import TimeFormatterRegistry from './TimeFormatterRegistry';
|
||||||
|
import TimeFormatter from './TimeFormatter';
|
||||||
|
import TimeFormatsForGranularity from './TimeFormatsForGranularity';
|
||||||
|
import { LOCAL_PREFIX } from './TimeFormats';
|
||||||
|
import { TimeGranularity } from './types';
|
||||||
|
import createTimeRangeFromGranularity from './utils/createTimeRangeFromGranularity';
|
||||||
|
import TimeRangeFormatter from './TimeRangeFormatter';
|
||||||
|
|
||||||
const getInstance = makeSingleton(TimeFormatterRegistry);
|
const getInstance = makeSingleton(TimeFormatterRegistry);
|
||||||
|
|
||||||
export default getInstance;
|
export default getInstance;
|
||||||
|
|
||||||
export function getTimeFormatter(formatId?: string) {
|
export function getTimeRangeFormatter(formatId?: string) {
|
||||||
|
return new TimeRangeFormatter({
|
||||||
|
id: formatId || 'undefined',
|
||||||
|
formatFunc: (range: (Date | number | null | undefined)[]) => {
|
||||||
|
const format = getInstance().get(formatId);
|
||||||
|
const [start, end] = range.map(value => format(value));
|
||||||
|
return start === end ? start : [start, end].join(' — ');
|
||||||
|
},
|
||||||
|
useLocalTime: formatId?.startsWith(LOCAL_PREFIX),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTimeRange(formatId: string | undefined, range: (Date | null | undefined)[]) {
|
||||||
|
return getTimeRangeFormatter(formatId)(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTimeFormatter(formatId?: string, granularity?: TimeGranularity) {
|
||||||
|
if (granularity) {
|
||||||
|
const formatString = formatId || TimeFormatsForGranularity[granularity];
|
||||||
|
const timeRangeFormatter = getTimeRangeFormatter(formatString);
|
||||||
|
|
||||||
|
return new TimeFormatter({
|
||||||
|
id: [formatString, granularity].join('/'),
|
||||||
|
formatFunc: (value: Date) =>
|
||||||
|
timeRangeFormatter.format(
|
||||||
|
createTimeRangeFromGranularity(value, granularity, timeRangeFormatter.useLocalTime),
|
||||||
|
),
|
||||||
|
useLocalTime: timeRangeFormatter.useLocalTime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return getInstance().get(formatId);
|
return getInstance().get(formatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatTime(formatId: string | undefined, value: Date | null | undefined) {
|
/**
|
||||||
return getInstance().format(formatId, value);
|
* Syntactic sugar for backward compatibility
|
||||||
|
* TODO: Deprecate this in the next breaking change.
|
||||||
|
* @param granularity
|
||||||
|
*/
|
||||||
|
export function getTimeFormatterForGranularity(granularity?: TimeGranularity) {
|
||||||
|
return getTimeFormatter(undefined, granularity);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTime(
|
||||||
|
formatId: string | undefined,
|
||||||
|
value: Date | null | undefined,
|
||||||
|
granularity?: TimeGranularity,
|
||||||
|
) {
|
||||||
|
return getTimeFormatter(formatId, granularity)(value);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import { ExtensibleFunction } from '@superset-ui/core';
|
||||||
|
import { TimeRangeFormatFunction } from './types';
|
||||||
|
|
||||||
|
// Use type augmentation to indicate that
|
||||||
|
// an instance of TimeFormatter is also a function
|
||||||
|
interface TimeRangeFormatter {
|
||||||
|
(value: (Date | number | null | undefined)[]): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimeRangeFormatter extends ExtensibleFunction {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
formatFunc: TimeRangeFormatFunction;
|
||||||
|
|
||||||
|
useLocalTime: boolean;
|
||||||
|
|
||||||
|
constructor(config: {
|
||||||
|
id: string;
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
formatFunc: TimeRangeFormatFunction;
|
||||||
|
useLocalTime?: boolean;
|
||||||
|
}) {
|
||||||
|
super((value: (Date | number | null | undefined)[]) => this.format(value));
|
||||||
|
|
||||||
|
const { id, label, description = '', formatFunc, useLocalTime = false } = config;
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
this.label = label ?? id;
|
||||||
|
this.description = description;
|
||||||
|
this.formatFunc = formatFunc;
|
||||||
|
this.useLocalTime = useLocalTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
format(values: (Date | number | null | undefined)[]) {
|
||||||
|
return this.formatFunc(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TimeRangeFormatter;
|
@ -1,5 +1,5 @@
|
|||||||
import { utcFormat, timeFormat } from 'd3-time-format';
|
import { utcFormat, timeFormat } from 'd3-time-format';
|
||||||
import { utcUtils, localTimeUtils } from '../utils';
|
import { utcUtils, localTimeUtils } from '../utils/d3Time';
|
||||||
import TimeFormatter from '../TimeFormatter';
|
import TimeFormatter from '../TimeFormatter';
|
||||||
|
|
||||||
type FormatsByStep = Partial<{
|
type FormatsByStep = Partial<{
|
||||||
|
@ -1,53 +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 TimeFormats from '../TimeFormats';
|
|
||||||
import { getTimeFormatter } from '../TimeFormatterRegistrySingleton';
|
|
||||||
import smartDateVerboseFormatter from '../formatters/smartDateVerbose';
|
|
||||||
import { TimeGranularity } from '../types';
|
|
||||||
|
|
||||||
// Translate time granularity to d3-format
|
|
||||||
const MINUTE = '%Y-%m-%d %H:%M';
|
|
||||||
const { DATABASE_DATE, DATABASE_DATETIME } = TimeFormats;
|
|
||||||
|
|
||||||
// search for `builtin_time_grains` in incubator-superset/superset/db_engine_specs/base.py
|
|
||||||
const formats = {
|
|
||||||
date: DATABASE_DATE,
|
|
||||||
PT1S: DATABASE_DATETIME, // second
|
|
||||||
PT1M: MINUTE, // minute
|
|
||||||
PT5M: MINUTE, // 5 minute
|
|
||||||
PT10M: MINUTE, // 10 minute
|
|
||||||
PT15M: MINUTE, // 15 minute
|
|
||||||
'PT0.5H': MINUTE, // half hour
|
|
||||||
PT1H: '%Y-%m-%d %H:00', // hour
|
|
||||||
P1D: DATABASE_DATE, // day
|
|
||||||
P1W: DATABASE_DATE, // week
|
|
||||||
P1M: '%Y-%m', // month
|
|
||||||
'P0.25Y': '%Y Q%q', // quarter
|
|
||||||
P1Y: '%Y', // year
|
|
||||||
'1969-12-28T00:00:00Z/P1W': DATABASE_DATE, // 'week_start_sunday'
|
|
||||||
'1969-12-29T00:00:00Z/P1W': DATABASE_DATE, // 'week_start_monday'
|
|
||||||
'P1W/1970-01-03T00:00:00Z': DATABASE_DATE, // 'week_ending_saturday'
|
|
||||||
'P1W/1970-01-04T00:00:00Z': DATABASE_DATE, // 'week_ending_sunday'
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function getTimeFormatterForGranularity(granularity?: TimeGranularity) {
|
|
||||||
return granularity && granularity in formats
|
|
||||||
? getTimeFormatter(formats[granularity])
|
|
||||||
: smartDateVerboseFormatter;
|
|
||||||
}
|
|
@ -4,12 +4,14 @@ export { default as TimeFormatter, PREVIEW_TIME } from './TimeFormatter';
|
|||||||
export {
|
export {
|
||||||
default as getTimeFormatterRegistry,
|
default as getTimeFormatterRegistry,
|
||||||
formatTime,
|
formatTime,
|
||||||
|
formatTimeRange,
|
||||||
getTimeFormatter,
|
getTimeFormatter,
|
||||||
|
getTimeFormatterForGranularity,
|
||||||
|
getTimeRangeFormatter,
|
||||||
} from './TimeFormatterRegistrySingleton';
|
} from './TimeFormatterRegistrySingleton';
|
||||||
|
|
||||||
export { default as createD3TimeFormatter } from './factories/createD3TimeFormatter';
|
export { default as createD3TimeFormatter } from './factories/createD3TimeFormatter';
|
||||||
export { default as createMultiFormatter } from './factories/createMultiFormatter';
|
export { default as createMultiFormatter } from './factories/createMultiFormatter';
|
||||||
export { default as getTimeFormatterForGranularity } from './factories/getTimeFormatterForGranularity';
|
|
||||||
|
|
||||||
export { default as smartDateFormatter } from './formatters/smartDate';
|
export { default as smartDateFormatter } from './formatters/smartDate';
|
||||||
export { default as smartDateVerboseFormatter } from './formatters/smartDateVerbose';
|
export { default as smartDateVerboseFormatter } from './formatters/smartDateVerbose';
|
||||||
|
@ -1,20 +1,30 @@
|
|||||||
export type TimeFormatFunction = (value: Date) => string;
|
export type TimeFormatFunction = (value: Date) => string;
|
||||||
|
|
||||||
export type TimeGranularity =
|
export type TimeRangeFormatFunction = (values: (Date | number | undefined | null)[]) => string;
|
||||||
| 'date'
|
|
||||||
| 'PT1S'
|
/**
|
||||||
| 'PT1M'
|
* search for `builtin_time_grains` in incubator-superset/superset/db_engine_specs/base.py
|
||||||
| 'PT5M'
|
*/
|
||||||
| 'PT10M'
|
export const TimeGranularity = {
|
||||||
| 'PT15M'
|
DATE: 'date',
|
||||||
| 'PT0.5H'
|
SECOND: 'PT1S',
|
||||||
| 'PT1H'
|
MINUTE: 'PT1M',
|
||||||
| 'P1D'
|
FIVE_MINUTES: 'PT5M',
|
||||||
| 'P1W'
|
TEN_MINUTES: 'PT10M',
|
||||||
| 'P1M'
|
FIFTEEN_MINUTES: 'PT15M',
|
||||||
| 'P0.25Y'
|
HALF_HOUR: 'PT0.5H',
|
||||||
| 'P1Y'
|
HOUR: 'PT1H',
|
||||||
| '1969-12-28T00:00:00Z/P1W'
|
DAY: 'P1D',
|
||||||
| '1969-12-29T00:00:00Z/P1W'
|
WEEK: 'P1W',
|
||||||
| 'P1W/1970-01-03T00:00:00Z'
|
WEEK_STARTING_SUNDAY: '1969-12-28T00:00:00Z/P1W',
|
||||||
| 'P1W/1970-01-04T00:00:00Z';
|
WEEK_STARTING_MONDAY: '1969-12-29T00:00:00Z/P1W',
|
||||||
|
WEEK_ENDING_SATURDAY: 'P1W/1970-01-03T00:00:00Z',
|
||||||
|
WEEK_ENDING_SUNDAY: 'P1W/1970-01-04T00:00:00Z',
|
||||||
|
MONTH: 'P1M',
|
||||||
|
QUARTER: 'P0.25Y',
|
||||||
|
YEAR: 'P1Y',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type ValueOf<T> = T[keyof T];
|
||||||
|
|
||||||
|
export type TimeGranularity = ValueOf<typeof TimeGranularity>;
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
export default function createTime(
|
||||||
|
mode: 'local' | 'utc',
|
||||||
|
year: number,
|
||||||
|
month: number = 0,
|
||||||
|
date: number = 1,
|
||||||
|
hours: number = 0,
|
||||||
|
minutes: number = 0,
|
||||||
|
seconds: number = 0,
|
||||||
|
milliseconds: number = 0,
|
||||||
|
): Date {
|
||||||
|
const args = [year, month, date, hours, minutes, seconds, milliseconds] as const;
|
||||||
|
return mode === 'local' ? new Date(...args) : new Date(Date.UTC(...args));
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
import { TimeGranularity } from '../types';
|
||||||
|
import createTime from './createTime';
|
||||||
|
|
||||||
|
const MS_IN_SECOND = 1000;
|
||||||
|
const MS_IN_MINUTE = 60 * MS_IN_SECOND;
|
||||||
|
const MS_IN_HOUR = 60 * MS_IN_MINUTE;
|
||||||
|
|
||||||
|
function deductOneMs(time: Date) {
|
||||||
|
return new Date(time.getTime() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeEndTimeFromGranularity(
|
||||||
|
time: Date,
|
||||||
|
granularity: TimeGranularity,
|
||||||
|
useLocalTime: boolean,
|
||||||
|
) {
|
||||||
|
const date = useLocalTime ? time.getDate() : time.getUTCDate();
|
||||||
|
const month = useLocalTime ? time.getMonth() : time.getUTCMonth();
|
||||||
|
const year = useLocalTime ? time.getFullYear() : time.getUTCFullYear();
|
||||||
|
const mode = useLocalTime ? 'local' : 'utc';
|
||||||
|
|
||||||
|
switch (granularity) {
|
||||||
|
case TimeGranularity.SECOND:
|
||||||
|
return new Date(time.getTime() + MS_IN_SECOND - 1);
|
||||||
|
case TimeGranularity.MINUTE:
|
||||||
|
return new Date(time.getTime() + MS_IN_MINUTE - 1);
|
||||||
|
case TimeGranularity.FIVE_MINUTES:
|
||||||
|
return new Date(time.getTime() + MS_IN_MINUTE * 5 - 1);
|
||||||
|
case TimeGranularity.TEN_MINUTES:
|
||||||
|
return new Date(time.getTime() + MS_IN_MINUTE * 10 - 1);
|
||||||
|
case TimeGranularity.FIFTEEN_MINUTES:
|
||||||
|
return new Date(time.getTime() + MS_IN_MINUTE * 15 - 1);
|
||||||
|
case TimeGranularity.HALF_HOUR:
|
||||||
|
return new Date(time.getTime() + MS_IN_MINUTE * 30 - 1);
|
||||||
|
case TimeGranularity.HOUR:
|
||||||
|
return new Date(time.getTime() + MS_IN_HOUR - 1);
|
||||||
|
// For the day granularity and above, using Date overflow is better than adding timestamp
|
||||||
|
// because it will also handle daylight saving.
|
||||||
|
case TimeGranularity.WEEK:
|
||||||
|
case TimeGranularity.WEEK_STARTING_SUNDAY:
|
||||||
|
case TimeGranularity.WEEK_STARTING_MONDAY:
|
||||||
|
return deductOneMs(createTime(mode, year, month, date + 7));
|
||||||
|
case TimeGranularity.MONTH:
|
||||||
|
return deductOneMs(createTime(mode, year, month + 1));
|
||||||
|
case TimeGranularity.QUARTER:
|
||||||
|
return deductOneMs(createTime(mode, year, (Math.floor(month / 3) + 1) * 3));
|
||||||
|
case TimeGranularity.YEAR:
|
||||||
|
return deductOneMs(createTime(mode, year + 1));
|
||||||
|
// For the WEEK_ENDING_XXX cases,
|
||||||
|
// currently assume "time" returned from database is supposed to be the end time
|
||||||
|
// (in contrast to all other granularities that the returned time is start time).
|
||||||
|
// However, the returned "time" is at 00:00:00.000, so have to add 23:59:59.999.
|
||||||
|
case TimeGranularity.WEEK_ENDING_SATURDAY:
|
||||||
|
case TimeGranularity.WEEK_ENDING_SUNDAY:
|
||||||
|
case TimeGranularity.DATE:
|
||||||
|
case TimeGranularity.DAY:
|
||||||
|
default:
|
||||||
|
return deductOneMs(createTime(mode, year, month, date + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createTimeRangeFromGranularity(
|
||||||
|
time: Date,
|
||||||
|
granularity: TimeGranularity,
|
||||||
|
useLocalTime: boolean = false,
|
||||||
|
) {
|
||||||
|
const endTime = computeEndTimeFromGranularity(time, granularity, useLocalTime);
|
||||||
|
|
||||||
|
if (
|
||||||
|
granularity === TimeGranularity.WEEK_ENDING_SATURDAY ||
|
||||||
|
granularity === TimeGranularity.WEEK_ENDING_SUNDAY
|
||||||
|
) {
|
||||||
|
const date = useLocalTime ? time.getDate() : time.getUTCDate();
|
||||||
|
const month = useLocalTime ? time.getMonth() : time.getUTCMonth();
|
||||||
|
const year = useLocalTime ? time.getFullYear() : time.getUTCFullYear();
|
||||||
|
const startTime = createTime(useLocalTime ? 'local' : 'utc', year, month, date - 6);
|
||||||
|
return [startTime, endTime];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [time, endTime];
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
export default function stringifyTimeInput(
|
||||||
|
value: Date | number | undefined | null,
|
||||||
|
fn: (time: Date) => string,
|
||||||
|
) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return `${value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(value instanceof Date ? value : new Date(value));
|
||||||
|
}
|
@ -1,9 +1,12 @@
|
|||||||
import getTimeFormatterRegistry, {
|
import getTimeFormatterRegistry, {
|
||||||
getTimeFormatter,
|
getTimeFormatter,
|
||||||
formatTime,
|
formatTime,
|
||||||
|
getTimeFormatterForGranularity,
|
||||||
|
formatTimeRange,
|
||||||
} from '../src/TimeFormatterRegistrySingleton';
|
} from '../src/TimeFormatterRegistrySingleton';
|
||||||
import TimeFormatterRegistry from '../src/TimeFormatterRegistry';
|
import TimeFormatterRegistry from '../src/TimeFormatterRegistry';
|
||||||
import { PREVIEW_TIME } from '../src/TimeFormatter';
|
import { PREVIEW_TIME } from '../src/TimeFormatter';
|
||||||
|
import { TimeGranularity, LOCAL_PREFIX } from '../src';
|
||||||
|
|
||||||
describe('TimeFormatterRegistrySingleton', () => {
|
describe('TimeFormatterRegistrySingleton', () => {
|
||||||
describe('getTimeFormatterRegistry()', () => {
|
describe('getTimeFormatterRegistry()', () => {
|
||||||
@ -17,17 +20,114 @@ describe('TimeFormatterRegistrySingleton', () => {
|
|||||||
expect(format(PREVIEW_TIME)).toEqual('14/02/2017');
|
expect(format(PREVIEW_TIME)).toEqual('14/02/2017');
|
||||||
});
|
});
|
||||||
it('falls back to default format if format is not specified', () => {
|
it('falls back to default format if format is not specified', () => {
|
||||||
const formatter = getTimeFormatter();
|
const format = getTimeFormatter();
|
||||||
expect(formatter.format(PREVIEW_TIME)).toEqual('2017-02-14 11:22:33');
|
expect(format(PREVIEW_TIME)).toEqual('2017-02-14 11:22:33');
|
||||||
|
});
|
||||||
|
it(`use local time when format string has LOCAL_PREFIX (${LOCAL_PREFIX})`, () => {
|
||||||
|
const format = getTimeFormatter('local!%m-%d %H:%M');
|
||||||
|
expect(format(new Date(2019, 5, 18, 11, 23))).toEqual('06-18 11:23');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('formatTime(format, value)', () => {
|
describe('getTimeFormatterForGranularity(granularity?)', () => {
|
||||||
it('format the given time using the specified format', () => {
|
it('returns the default formatter for that granularity', () => {
|
||||||
const output = formatTime('%Y-%m-%d', PREVIEW_TIME);
|
const date = new Date(Date.UTC(2020, 4, 10)); // May 10, 2020 is Sunday
|
||||||
expect(output).toEqual('2017-02-14');
|
expect(getTimeFormatterForGranularity(TimeGranularity.DATE)(date)).toEqual('2020-05-10');
|
||||||
});
|
});
|
||||||
it('falls back to the default formatter if the format is undefined', () => {
|
});
|
||||||
expect(formatTime(undefined, PREVIEW_TIME)).toEqual('2017-02-14 11:22:33');
|
describe('formatTimeRange(format?, values)', () => {
|
||||||
|
it('format the given time range with specified format', () => {
|
||||||
|
expect(
|
||||||
|
formatTimeRange('%m-%d', [new Date(Date.UTC(2017, 1, 1)), new Date(Date.UTC(2017, 1, 2))]),
|
||||||
|
).toEqual('02-01 — 02-02');
|
||||||
|
});
|
||||||
|
it('show only one value if start and end are equal after formatting', () => {
|
||||||
|
expect(
|
||||||
|
formatTimeRange('%m-%d', [
|
||||||
|
new Date(Date.UTC(2017, 1, 1)),
|
||||||
|
new Date(Date.UTC(2017, 1, 1, 10)),
|
||||||
|
]),
|
||||||
|
).toEqual('02-01');
|
||||||
|
});
|
||||||
|
it('falls back to default format if format is not specified', () => {
|
||||||
|
expect(
|
||||||
|
formatTimeRange(undefined, [
|
||||||
|
new Date(Date.UTC(2017, 1, 1)),
|
||||||
|
new Date(Date.UTC(2017, 1, 2)),
|
||||||
|
]),
|
||||||
|
).toEqual('2017-02-01 00:00:00 — 2017-02-02 00:00:00');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('formatTime(format?, value, granularity?)', () => {
|
||||||
|
describe('without granularity', () => {
|
||||||
|
it('format the given time using the specified format', () => {
|
||||||
|
const output = formatTime('%Y-%m-%d', PREVIEW_TIME);
|
||||||
|
expect(output).toEqual('2017-02-14');
|
||||||
|
});
|
||||||
|
it('falls back to the default formatter if the format is undefined', () => {
|
||||||
|
expect(formatTime(undefined, PREVIEW_TIME)).toEqual('2017-02-14 11:22:33');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('with granularity', () => {
|
||||||
|
it('format the given time using specified format', () => {
|
||||||
|
const output = formatTime('%-m/%d', new Date(Date.UTC(2017, 4, 10)), TimeGranularity.WEEK);
|
||||||
|
expect(output).toEqual('5/10 — 5/16');
|
||||||
|
});
|
||||||
|
it('format the given time using default format if format is not specified', () => {
|
||||||
|
const date = new Date(Date.UTC(2020, 4, 10)); // May 10, 2020 is Sunday
|
||||||
|
expect(formatTime(undefined, date, TimeGranularity.DATE)).toEqual('2020-05-10');
|
||||||
|
expect(formatTime(undefined, date, TimeGranularity.SECOND)).toEqual('2020-05-10 00:00:00');
|
||||||
|
expect(formatTime(undefined, date, TimeGranularity.MINUTE)).toEqual('2020-05-10 00:00');
|
||||||
|
expect(formatTime(undefined, date, TimeGranularity.FIVE_MINUTES)).toEqual(
|
||||||
|
'2020-05-10 00:00 — 2020-05-10 00:04',
|
||||||
|
);
|
||||||
|
expect(formatTime(undefined, date, TimeGranularity.TEN_MINUTES)).toEqual(
|
||||||
|
'2020-05-10 00:00 — 2020-05-10 00:09',
|
||||||
|
);
|
||||||
|
expect(formatTime(undefined, date, TimeGranularity.FIFTEEN_MINUTES)).toEqual(
|
||||||
|
'2020-05-10 00:00 — 2020-05-10 00:14',
|
||||||
|
);
|
||||||
|
expect(formatTime(undefined, date, TimeGranularity.HALF_HOUR)).toEqual(
|
||||||
|
'2020-05-10 00:00 — 2020-05-10 00:29',
|
||||||
|
);
|
||||||
|
expect(formatTime(undefined, date, TimeGranularity.HOUR)).toEqual('2020-05-10 00:00');
|
||||||
|
expect(formatTime(undefined, date, TimeGranularity.DAY)).toEqual('2020-05-10');
|
||||||
|
expect(formatTime(undefined, date, TimeGranularity.WEEK)).toEqual(
|
||||||
|
'2020-05-10 — 2020-05-16',
|
||||||
|
);
|
||||||
|
expect(formatTime(undefined, date, TimeGranularity.WEEK_STARTING_SUNDAY)).toEqual(
|
||||||
|
'2020-05-10 — 2020-05-16',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
formatTime(
|
||||||
|
undefined,
|
||||||
|
new Date(Date.UTC(2020, 4, 11)),
|
||||||
|
TimeGranularity.WEEK_STARTING_MONDAY,
|
||||||
|
),
|
||||||
|
).toEqual('2020-05-11 — 2020-05-17');
|
||||||
|
expect(
|
||||||
|
formatTime(
|
||||||
|
undefined,
|
||||||
|
new Date(Date.UTC(2020, 4, 10)),
|
||||||
|
TimeGranularity.WEEK_ENDING_SUNDAY,
|
||||||
|
),
|
||||||
|
).toEqual('2020-05-04 — 2020-05-10');
|
||||||
|
expect(
|
||||||
|
formatTime(
|
||||||
|
undefined,
|
||||||
|
new Date(Date.UTC(2020, 4, 9)),
|
||||||
|
TimeGranularity.WEEK_ENDING_SATURDAY,
|
||||||
|
),
|
||||||
|
).toEqual('2020-05-03 — 2020-05-09');
|
||||||
|
expect(
|
||||||
|
formatTime(undefined, new Date(Date.UTC(2020, 3, 1)), TimeGranularity.MONTH),
|
||||||
|
).toEqual('Apr 2020');
|
||||||
|
expect(
|
||||||
|
formatTime(undefined, new Date(Date.UTC(2020, 3, 1)), TimeGranularity.QUARTER),
|
||||||
|
).toEqual('2020 Q2');
|
||||||
|
expect(formatTime(undefined, new Date(Date.UTC(2020, 0, 1)), TimeGranularity.YEAR)).toEqual(
|
||||||
|
'2020',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
import getFormatter from '../../src/factories/getTimeFormatterForGranularity';
|
|
||||||
import smartDateVerbose from '../../src/formatters/smartDateVerbose';
|
|
||||||
|
|
||||||
describe('getTimeFormatterForGranularity()', () => {
|
|
||||||
it('use smartDate when granularity unknown or undefined', () => {
|
|
||||||
expect(getFormatter(undefined)).toBe(smartDateVerbose);
|
|
||||||
// @ts-ignore
|
|
||||||
expect(getFormatter('random-string')).toBe(smartDateVerbose);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('format time for known granularities', () => {
|
|
||||||
// JS Date constructor month is zero-based
|
|
||||||
const date = new Date(2020, 4, 10, 11, 10, 1); // May 10, 2020 is Sunday
|
|
||||||
expect(getFormatter('date')(date)).toBe('2020-05-10');
|
|
||||||
expect(getFormatter('PT1S')(date)).toBe('2020-05-10 11:10:01');
|
|
||||||
expect(getFormatter('PT1M')(date)).toBe('2020-05-10 11:10');
|
|
||||||
expect(getFormatter('PT5M')(date)).toBe('2020-05-10 11:10');
|
|
||||||
expect(getFormatter('PT10M')(date)).toBe('2020-05-10 11:10');
|
|
||||||
expect(getFormatter('PT15M')(date)).toBe('2020-05-10 11:10');
|
|
||||||
expect(getFormatter('PT0.5H')(date)).toBe('2020-05-10 11:10');
|
|
||||||
expect(getFormatter('PT1H')(date)).toBe('2020-05-10 11:00');
|
|
||||||
expect(getFormatter('P1D')(date)).toBe('2020-05-10');
|
|
||||||
expect(getFormatter('P1W')(date)).toBe('2020-05-10');
|
|
||||||
expect(getFormatter('P1M')(date)).toBe('2020-05');
|
|
||||||
expect(getFormatter('P0.25Y')(date)).toBe('2020 Q2');
|
|
||||||
expect(getFormatter('P1Y')(date)).toBe('2020');
|
|
||||||
// sunday based week
|
|
||||||
expect(getFormatter('1969-12-28T00:00:00Z/P1W')(date)).toBe('2020-05-10');
|
|
||||||
expect(getFormatter('P1W/1970-01-03T00:00:00Z')(date)).toBe('2020-05-10');
|
|
||||||
// monday based week
|
|
||||||
expect(getFormatter('1969-12-29T00:00:00Z/P1W')(date)).toBe('2020-05-10');
|
|
||||||
expect(getFormatter('P1W/1970-01-04T00:00:00Z')(date)).toBe('2020-05-10');
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,37 @@
|
|||||||
|
import createTime from '../../src/utils/createTime';
|
||||||
|
|
||||||
|
describe('createTime(mode, year, month, date, hours, minutes, seconds, milliseconds)', () => {
|
||||||
|
describe('mode', () => {
|
||||||
|
it('creates UTC time when mode==="utc"', () => {
|
||||||
|
const time = createTime('utc', 2020, 5, 15);
|
||||||
|
expect(time.getUTCFullYear()).toEqual(2020);
|
||||||
|
expect(time.getUTCMonth()).toEqual(5);
|
||||||
|
expect(time.getUTCDate()).toEqual(15);
|
||||||
|
});
|
||||||
|
it('creates local time when mode==="local"', () => {
|
||||||
|
const time = createTime('local', 2020, 5, 15);
|
||||||
|
expect(time.getFullYear()).toEqual(2020);
|
||||||
|
expect(time.getMonth()).toEqual(5);
|
||||||
|
expect(time.getDate()).toEqual(15);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('sets all the date parts', () => {
|
||||||
|
const time = createTime('local', 2020, 5, 15, 1, 2, 3, 4);
|
||||||
|
expect(time.getFullYear()).toEqual(2020);
|
||||||
|
expect(time.getMonth()).toEqual(5);
|
||||||
|
expect(time.getDate()).toEqual(15);
|
||||||
|
expect(time.getHours()).toEqual(1);
|
||||||
|
expect(time.getMinutes()).toEqual(2);
|
||||||
|
expect(time.getSeconds()).toEqual(3);
|
||||||
|
expect(time.getMilliseconds()).toEqual(4);
|
||||||
|
});
|
||||||
|
it('sets default values for date parts', () => {
|
||||||
|
const time = createTime('utc', 2020);
|
||||||
|
expect(time.getUTCMonth()).toEqual(0);
|
||||||
|
expect(time.getUTCDate()).toEqual(1);
|
||||||
|
expect(time.getUTCHours()).toEqual(0);
|
||||||
|
expect(time.getUTCMinutes()).toEqual(0);
|
||||||
|
expect(time.getUTCSeconds()).toEqual(0);
|
||||||
|
expect(time.getUTCMilliseconds()).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,166 @@
|
|||||||
|
import createTimeRangeFromGranularity from '../../src/utils/createTimeRangeFromGranularity';
|
||||||
|
import { TimeGranularity, getTimeRangeFormatter, LOCAL_PREFIX } from '../../src';
|
||||||
|
|
||||||
|
const formatString = '%Y-%m-%d %H:%M:%S.%L';
|
||||||
|
const formatUTCTimeRange = getTimeRangeFormatter(formatString);
|
||||||
|
const formatLocalTimeRange = getTimeRangeFormatter(`${LOCAL_PREFIX}${formatString}`);
|
||||||
|
|
||||||
|
function testUTC(
|
||||||
|
granularity: TimeGranularity,
|
||||||
|
year: number,
|
||||||
|
month: number = 0,
|
||||||
|
date: number = 1,
|
||||||
|
hours: number = 0,
|
||||||
|
minutes: number = 0,
|
||||||
|
seconds: number = 0,
|
||||||
|
) {
|
||||||
|
return formatUTCTimeRange(
|
||||||
|
createTimeRangeFromGranularity(
|
||||||
|
new Date(Date.UTC(year, month, date, hours, minutes, seconds)),
|
||||||
|
granularity,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testLocal(
|
||||||
|
granularity: TimeGranularity,
|
||||||
|
year: number,
|
||||||
|
month: number = 0,
|
||||||
|
date: number = 1,
|
||||||
|
hours: number = 0,
|
||||||
|
minutes: number = 0,
|
||||||
|
seconds: number = 0,
|
||||||
|
) {
|
||||||
|
return formatLocalTimeRange(
|
||||||
|
createTimeRangeFromGranularity(
|
||||||
|
new Date(year, month, date, hours, minutes, seconds),
|
||||||
|
granularity,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('createTimeRangeFromGranularity(time, granularity, useLocalTime)', () => {
|
||||||
|
describe('UTC time', () => {
|
||||||
|
it('creates time range according to specified granularity', () => {
|
||||||
|
expect(testUTC(TimeGranularity.DATE, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.SECOND, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:00:00.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.MINUTE, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:00:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.FIVE_MINUTES, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:04:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.TEN_MINUTES, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:09:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.FIFTEEN_MINUTES, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:14:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.HALF_HOUR, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:29:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.HOUR, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.DAY, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.WEEK, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-21 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.WEEK_STARTING_SUNDAY, 2020, 4, 17)).toEqual(
|
||||||
|
'2020-05-17 00:00:00.000 — 2020-05-23 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.WEEK_STARTING_MONDAY, 2020, 4, 18)).toEqual(
|
||||||
|
'2020-05-18 00:00:00.000 — 2020-05-24 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.WEEK_ENDING_SATURDAY, 2020, 4, 16)).toEqual(
|
||||||
|
'2020-05-10 00:00:00.000 — 2020-05-16 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.WEEK_ENDING_SUNDAY, 2020, 4, 17)).toEqual(
|
||||||
|
'2020-05-11 00:00:00.000 — 2020-05-17 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.MONTH, 2020, 4, 1)).toEqual(
|
||||||
|
'2020-05-01 00:00:00.000 — 2020-05-31 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.MONTH, 2020, 11, 1)).toEqual(
|
||||||
|
'2020-12-01 00:00:00.000 — 2020-12-31 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.QUARTER, 2020, 3, 1)).toEqual(
|
||||||
|
'2020-04-01 00:00:00.000 — 2020-06-30 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.QUARTER, 2020, 9, 1)).toEqual(
|
||||||
|
'2020-10-01 00:00:00.000 — 2020-12-31 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testUTC(TimeGranularity.YEAR, 2020, 0, 1)).toEqual(
|
||||||
|
'2020-01-01 00:00:00.000 — 2020-12-31 23:59:59.999',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Local time', () => {
|
||||||
|
it('creates time range according to specified granularity', () => {
|
||||||
|
expect(testLocal(TimeGranularity.DATE, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.SECOND, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:00:00.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.MINUTE, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:00:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.FIVE_MINUTES, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:04:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.TEN_MINUTES, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:09:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.FIFTEEN_MINUTES, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:14:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.HALF_HOUR, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:29:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.HOUR, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 00:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.DAY, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-15 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.WEEK, 2020, 4, 15)).toEqual(
|
||||||
|
'2020-05-15 00:00:00.000 — 2020-05-21 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.WEEK_STARTING_SUNDAY, 2020, 4, 17)).toEqual(
|
||||||
|
'2020-05-17 00:00:00.000 — 2020-05-23 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.WEEK_STARTING_MONDAY, 2020, 4, 18)).toEqual(
|
||||||
|
'2020-05-18 00:00:00.000 — 2020-05-24 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.WEEK_ENDING_SATURDAY, 2020, 4, 16)).toEqual(
|
||||||
|
'2020-05-10 00:00:00.000 — 2020-05-16 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.WEEK_ENDING_SUNDAY, 2020, 4, 17)).toEqual(
|
||||||
|
'2020-05-11 00:00:00.000 — 2020-05-17 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.MONTH, 2020, 4, 1)).toEqual(
|
||||||
|
'2020-05-01 00:00:00.000 — 2020-05-31 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.MONTH, 2020, 11, 1)).toEqual(
|
||||||
|
'2020-12-01 00:00:00.000 — 2020-12-31 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.QUARTER, 2020, 3, 1)).toEqual(
|
||||||
|
'2020-04-01 00:00:00.000 — 2020-06-30 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.QUARTER, 2020, 9, 1)).toEqual(
|
||||||
|
'2020-10-01 00:00:00.000 — 2020-12-31 23:59:59.999',
|
||||||
|
);
|
||||||
|
expect(testLocal(TimeGranularity.YEAR, 2020, 0, 1)).toEqual(
|
||||||
|
'2020-01-01 00:00:00.000 — 2020-12-31 23:59:59.999',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,4 +1,4 @@
|
|||||||
import { utcUtils, localTimeUtils } from '../src/utils';
|
import { utcUtils, localTimeUtils } from '../../src/utils/d3Time';
|
||||||
|
|
||||||
describe('utils', () => {
|
describe('utils', () => {
|
||||||
describe('utcUtils', () => {
|
describe('utcUtils', () => {
|
Loading…
Reference in New Issue
Block a user