mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
System: Date formating options (#27216)
* Add support for local time formats in graph panel * Enfore 24h format for backward compatibility * Use existing Intl.DateTimeFormatOptions * Pre-generate time scale, add tests * Move localTimeFormat, add local format to units * updated default fallback * #25602, use navigator.languages to enforce locale in formatting * Making options * Worked new system settings * things are working * Local browser time formats working * Support parsing dates in different formats * settings updated * Settings starting to work * Fixed graph issue * Logs fix * refactored settings a bit * Updated and name change * Progress * Changed config names * Updated * Updated * Updated test * Synced description * fixed ts issue * Added version notice * Ts fix * Updated heatmap and test * Updated snapshot * Updated * fixed ts issue * Fixes Co-authored-by: Alex Shpak <alex-shpak@users.noreply.github.com>
This commit is contained in:
parent
783391a861
commit
61bd33c241
@ -803,3 +803,22 @@ license_path =
|
|||||||
[feature_toggles]
|
[feature_toggles]
|
||||||
# enable features, separated by spaces
|
# enable features, separated by spaces
|
||||||
enable =
|
enable =
|
||||||
|
|
||||||
|
[date_formats]
|
||||||
|
# For information on what formatting patterns that are supported https://momentjs.com/docs/#/displaying/
|
||||||
|
|
||||||
|
# Default system date format used in time range picker and other places where full time is displayed
|
||||||
|
full_date = YYYY-MM-DD HH:mm:ss
|
||||||
|
|
||||||
|
# Used by graph and other places where we only show small intervals
|
||||||
|
interval_second = HH:mm:ss
|
||||||
|
interval_minute = HH:mm
|
||||||
|
interval_hour = MM/DD HH:mm
|
||||||
|
interval_day = MM/DD
|
||||||
|
interval_month = YYYY-MM
|
||||||
|
interval_year = YYYY
|
||||||
|
|
||||||
|
# Experimental feature
|
||||||
|
use_browser_locale = false
|
||||||
|
|
||||||
|
|
||||||
|
@ -794,3 +794,20 @@
|
|||||||
[feature_toggles]
|
[feature_toggles]
|
||||||
# enable features, separated by spaces
|
# enable features, separated by spaces
|
||||||
;enable =
|
;enable =
|
||||||
|
|
||||||
|
[date_formats]
|
||||||
|
# For information on what formatting patterns that are supported https://momentjs.com/docs/#/displaying/
|
||||||
|
|
||||||
|
# Default system date format used in time range picker and other places where full time is displayed
|
||||||
|
;full_date = YYYY-MM-DD HH:mm:ss
|
||||||
|
|
||||||
|
# Used by graph and other places where we only show small intervals
|
||||||
|
;interval_second = HH:mm:ss
|
||||||
|
;interval_minute = HH:mm
|
||||||
|
;interval_hour = MM/DD HH:mm
|
||||||
|
;interval_day = MM/DD
|
||||||
|
;interval_month = YYYY-MM
|
||||||
|
;interval_year = YYYY
|
||||||
|
|
||||||
|
# Experimental feature
|
||||||
|
;use_browser_locale = false
|
||||||
|
@ -189,8 +189,6 @@ Folder that contains [provisioning]({{< relref "provisioning.md" >}}) config fil
|
|||||||
|
|
||||||
`http`,`https`,`h2` or `socket`
|
`http`,`https`,`h2` or `socket`
|
||||||
|
|
||||||
> **Note:** Grafana versions earlier than 3.0 are vulnerable to [POODLE](https://en.wikipedia.org/wiki/POODLE). So we strongly recommend to upgrade to 3.x or use a reverse proxy for SSL termination.
|
|
||||||
|
|
||||||
### http_addr
|
### http_addr
|
||||||
|
|
||||||
The IP address to bind to. If empty will bind to all interfaces
|
The IP address to bind to. If empty will bind to all interfaces
|
||||||
@ -231,8 +229,6 @@ callback URL to be correct).
|
|||||||
|
|
||||||
### serve_from_sub_path
|
### serve_from_sub_path
|
||||||
|
|
||||||
> Available in Grafana 6.3+.
|
|
||||||
|
|
||||||
Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons.
|
Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons.
|
||||||
|
|
||||||
By enabling this setting and using a subpath in `root_url` above, e.g.
|
By enabling this setting and using a subpath in `root_url` above, e.g.
|
||||||
@ -1387,3 +1383,37 @@ For more information about Grafana Enterprise, refer to [Grafana Enterprise]({{<
|
|||||||
### enable
|
### enable
|
||||||
|
|
||||||
Keys of alpha features to enable, separated by space. Available alpha features are: `transformations`, `standaloneAlerts`
|
Keys of alpha features to enable, separated by space. Available alpha features are: `transformations`, `standaloneAlerts`
|
||||||
|
|
||||||
|
## [date_formats]
|
||||||
|
|
||||||
|
> The date format options below are only available in Grafana v7.2+
|
||||||
|
|
||||||
|
This section controls system wide defaults for date formats used in time ranges, graphs and date input boxes.
|
||||||
|
|
||||||
|
The format patterns use [Moment.js](https://momentjs.com/docs/#/displaying/) formatting tokens.
|
||||||
|
|
||||||
|
### full_date
|
||||||
|
|
||||||
|
Full date format used by time range picker and in other places where a full date is rendered.
|
||||||
|
|
||||||
|
### intervals
|
||||||
|
|
||||||
|
These intervals formats are used in the graph to show only a partial date or time. For example if there are only
|
||||||
|
minutes between y-axis tick labels then the `interval_minute` format is used.
|
||||||
|
|
||||||
|
Defaults
|
||||||
|
|
||||||
|
```
|
||||||
|
interval_second = HH:mm:ss
|
||||||
|
interval_minute = HH:mm
|
||||||
|
interval_hour = MM/DD HH:mm
|
||||||
|
interval_day = MM/DD
|
||||||
|
interval_month = YYYY-MM
|
||||||
|
interval_year = YYYY
|
||||||
|
```
|
||||||
|
|
||||||
|
### use_browser_locale
|
||||||
|
|
||||||
|
Set this to `true` to have date formats be automatically be derived from browser locale. Defaults to `false`. This
|
||||||
|
is an experimental feature right now with a few problems that remain unsolved.
|
||||||
|
|
||||||
|
@ -13,6 +13,13 @@ export interface DateTimeOptions {
|
|||||||
* user is used.
|
* user is used.
|
||||||
*/
|
*/
|
||||||
timeZone?: TimeZone;
|
timeZone?: TimeZone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a {@link https://momentjs.com/docs/#/displaying/format | momentjs} format to
|
||||||
|
* use a custom formatting pattern or parsing pattern. If no format is set,
|
||||||
|
* then system configured default format is used.
|
||||||
|
*/
|
||||||
|
format?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
19
packages/grafana-data/src/datetime/formats.test.ts
Normal file
19
packages/grafana-data/src/datetime/formats.test.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { localTimeFormat } from './formats';
|
||||||
|
|
||||||
|
describe('Date Formats', () => {
|
||||||
|
it('localTimeFormat', () => {
|
||||||
|
const format = localTimeFormat(
|
||||||
|
{
|
||||||
|
year: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
},
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(format).toBe('MM/DD/YYYY, HH:mm:ss A');
|
||||||
|
});
|
||||||
|
});
|
@ -1,2 +1,117 @@
|
|||||||
export const DEFAULT_DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
export interface SystemDateFormatSettings {
|
||||||
export const MS_DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS';
|
fullDate: string;
|
||||||
|
interval: {
|
||||||
|
second: string;
|
||||||
|
minute: string;
|
||||||
|
hour: string;
|
||||||
|
day: string;
|
||||||
|
month: string;
|
||||||
|
year: string;
|
||||||
|
};
|
||||||
|
useBrowserLocale: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_SYSTEM_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
|
||||||
|
export class SystemDateFormatsState {
|
||||||
|
fullDate = DEFAULT_SYSTEM_DATE_FORMAT;
|
||||||
|
interval = {
|
||||||
|
second: 'HH:mm:ss',
|
||||||
|
minute: 'HH:mm',
|
||||||
|
hour: 'MM/DD HH:mm',
|
||||||
|
day: 'MM/DD',
|
||||||
|
month: 'YYYY-MM',
|
||||||
|
year: 'YYYY',
|
||||||
|
};
|
||||||
|
|
||||||
|
update(settings: SystemDateFormatSettings) {
|
||||||
|
this.fullDate = settings.fullDate;
|
||||||
|
this.interval = settings.interval;
|
||||||
|
|
||||||
|
if (settings.useBrowserLocale) {
|
||||||
|
this.useBrowserLocale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get fullDateMS() {
|
||||||
|
// Add millisecond to seconds part
|
||||||
|
return this.fullDate.replace('ss', 'ss.SSS');
|
||||||
|
}
|
||||||
|
|
||||||
|
useBrowserLocale() {
|
||||||
|
this.fullDate = localTimeFormat({
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.interval.second = localTimeFormat(
|
||||||
|
{ hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false },
|
||||||
|
null,
|
||||||
|
this.interval.second
|
||||||
|
);
|
||||||
|
this.interval.minute = localTimeFormat(
|
||||||
|
{ hour: '2-digit', minute: '2-digit', hour12: false },
|
||||||
|
null,
|
||||||
|
this.interval.minute
|
||||||
|
);
|
||||||
|
this.interval.hour = localTimeFormat(
|
||||||
|
{ month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false },
|
||||||
|
null,
|
||||||
|
this.interval.hour
|
||||||
|
);
|
||||||
|
this.interval.day = localTimeFormat({ month: '2-digit', day: '2-digit', hour12: false }, null, this.interval.day);
|
||||||
|
this.interval.month = localTimeFormat(
|
||||||
|
{ year: 'numeric', month: '2-digit', hour12: false },
|
||||||
|
null,
|
||||||
|
this.interval.month
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTimeFieldUnit(useMsResolution?: boolean) {
|
||||||
|
return `time:${useMsResolution ? this.fullDateMS : this.fullDate}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* localTimeFormat helps to generate date formats for momentjs based on browser's locale
|
||||||
|
*
|
||||||
|
* @param locale browser locale, or default
|
||||||
|
* @param options DateTimeFormatOptions to format date
|
||||||
|
* @param fallback default format if Intl API is not present
|
||||||
|
*/
|
||||||
|
export function localTimeFormat(
|
||||||
|
options: Intl.DateTimeFormatOptions,
|
||||||
|
locale?: string | string[] | null,
|
||||||
|
fallback?: string
|
||||||
|
): string {
|
||||||
|
if (!window.Intl) {
|
||||||
|
return fallback ?? DEFAULT_SYSTEM_DATE_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!locale) {
|
||||||
|
locale = [...navigator.languages];
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://momentjs.com/docs/#/displaying/format/
|
||||||
|
const parts = new Intl.DateTimeFormat(locale, options).formatToParts(new Date());
|
||||||
|
const mapping: { [key: string]: string } = {
|
||||||
|
year: 'YYYY',
|
||||||
|
month: 'MM',
|
||||||
|
day: 'DD',
|
||||||
|
hour: 'HH',
|
||||||
|
minute: 'mm',
|
||||||
|
second: 'ss',
|
||||||
|
weekday: 'ddd',
|
||||||
|
era: 'N',
|
||||||
|
dayPeriod: 'A',
|
||||||
|
timeZoneName: 'Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
return parts.map(part => mapping[part.type] || part.value).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
export const systemDateFormats = new SystemDateFormatsState();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import moment, { MomentInput, Moment } from 'moment-timezone';
|
import moment, { MomentInput, Moment } from 'moment-timezone';
|
||||||
import { TimeZone } from '../types';
|
import { TimeZone } from '../types';
|
||||||
import { DateTimeInput } from './moment_wrapper';
|
import { DateTimeInput } from './moment_wrapper';
|
||||||
import { DEFAULT_DATE_TIME_FORMAT, MS_DATE_TIME_FORMAT } from './formats';
|
import { systemDateFormats } from './formats';
|
||||||
import { DateTimeOptions, getTimeZone } from './common';
|
import { DateTimeOptions, getTimeZone } from './common';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -13,16 +13,8 @@ import { DateTimeOptions, getTimeZone } from './common';
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface DateTimeOptionsWithFormat extends DateTimeOptions {
|
export interface DateTimeOptionsWithFormat extends DateTimeOptions {
|
||||||
/**
|
|
||||||
* Specify a {@link https://momentjs.com/docs/#/displaying/format | momentjs} format to
|
|
||||||
* use a custom formatting pattern of the date and time value. If no format is set,
|
|
||||||
* then {@link DEFAULT_DATE_TIME_FORMAT} is used.
|
|
||||||
*/
|
|
||||||
format?: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set this value to `true` if you want to include milliseconds when formatting date and time
|
* Set this value to `true` if you want to include milliseconds when formatting date and time
|
||||||
* values in the default {@link DEFAULT_DATE_TIME_FORMAT} format.
|
|
||||||
*/
|
*/
|
||||||
defaultWithMS?: boolean;
|
defaultWithMS?: boolean;
|
||||||
}
|
}
|
||||||
@ -77,7 +69,7 @@ export const dateTimeFormatTimeAgo: DateTimeFormatter = (dateInUtc, options?) =>
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export const dateTimeFormatWithAbbrevation: DateTimeFormatter = (dateInUtc, options?) =>
|
export const dateTimeFormatWithAbbrevation: DateTimeFormatter = (dateInUtc, options?) =>
|
||||||
toTz(dateInUtc, getTimeZone(options)).format(`${DEFAULT_DATE_TIME_FORMAT} z`);
|
toTz(dateInUtc, getTimeZone(options)).format(`${systemDateFormats.fullDate} z`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to return only the time zone abbreviation for a given date and time value. If no options
|
* Helper function to return only the time zone abbreviation for a given date and time value. If no options
|
||||||
@ -93,9 +85,9 @@ export const timeZoneAbbrevation: DateTimeFormatter = (dateInUtc, options?) =>
|
|||||||
|
|
||||||
const getFormat = <T extends DateTimeOptionsWithFormat>(options?: T): string => {
|
const getFormat = <T extends DateTimeOptionsWithFormat>(options?: T): string => {
|
||||||
if (options?.defaultWithMS) {
|
if (options?.defaultWithMS) {
|
||||||
return options?.format ?? MS_DATE_TIME_FORMAT;
|
return options?.format ?? systemDateFormats.fullDateMS;
|
||||||
}
|
}
|
||||||
return options?.format ?? DEFAULT_DATE_TIME_FORMAT;
|
return options?.format ?? systemDateFormats.fullDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toTz = (dateInUtc: DateTimeInput, timeZone: TimeZone): Moment => {
|
const toTz = (dateInUtc: DateTimeInput, timeZone: TimeZone): Moment => {
|
||||||
|
25
packages/grafana-data/src/datetime/parser.test.ts
Normal file
25
packages/grafana-data/src/datetime/parser.test.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { dateTimeParse } from './parser';
|
||||||
|
import { systemDateFormats } from './formats';
|
||||||
|
|
||||||
|
describe('dateTimeParse', () => {
|
||||||
|
it('should be able to parse using default format', () => {
|
||||||
|
const date = dateTimeParse('2020-03-02 15:00:22', { timeZone: 'utc' });
|
||||||
|
expect(date.format()).toEqual('2020-03-02T15:00:22Z');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to parse using default format', () => {
|
||||||
|
systemDateFormats.update({
|
||||||
|
fullDate: 'MMMM D, YYYY, h:mm:ss a',
|
||||||
|
interval: {} as any,
|
||||||
|
useBrowserLocale: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const date = dateTimeParse('Aug 20, 2020 10:30:20 am', { timeZone: 'utc' });
|
||||||
|
expect(date.format()).toEqual('2020-08-20T10:30:20Z');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to parse array formats used by calendar', () => {
|
||||||
|
const date = dateTimeParse([2020, 5, 10, 10, 30, 20], { timeZone: 'utc' });
|
||||||
|
expect(date.format()).toEqual('2020-06-10T10:30:20Z');
|
||||||
|
});
|
||||||
|
});
|
@ -4,6 +4,7 @@ import { DateTimeInput, DateTime, isDateTime } from './moment_wrapper';
|
|||||||
import { DateTimeOptions, getTimeZone } from './common';
|
import { DateTimeOptions, getTimeZone } from './common';
|
||||||
import { parse, isValid } from './datemath';
|
import { parse, isValid } from './datemath';
|
||||||
import { lowerCase } from 'lodash';
|
import { lowerCase } from 'lodash';
|
||||||
|
import { systemDateFormats } from './formats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type that describes options that can be passed when parsing a date and time value.
|
* The type that describes options that can be passed when parsing a date and time value.
|
||||||
@ -25,7 +26,7 @@ type DateTimeParser<T extends DateTimeOptions = DateTimeOptions> = (value: DateT
|
|||||||
/**
|
/**
|
||||||
* Helper function to parse a number, text or Date to a DateTime value. If a timeZone is supplied the incoming value
|
* Helper function to parse a number, text or Date to a DateTime value. If a timeZone is supplied the incoming value
|
||||||
* is parsed with that timeZone as a base. The only exception to this is if the passed value is in a UTC-based
|
* is parsed with that timeZone as a base. The only exception to this is if the passed value is in a UTC-based
|
||||||
* format. Then it will use UTC as the base. Examples on UTC-based values are Unix epoch and ISO formatted strings.
|
* format. Then it will use UTC as the base. If no format is specified the current system format will be assumed.
|
||||||
*
|
*
|
||||||
* It can also parse the Grafana quick date and time format, e.g. now-6h will be parsed as Date.now() - 6 hours and
|
* It can also parse the Grafana quick date and time format, e.g. now-6h will be parsed as Date.now() - 6 hours and
|
||||||
* returned as a valid DateTime value.
|
* returned as a valid DateTime value.
|
||||||
@ -59,7 +60,20 @@ const parseString = (value: string, options?: DateTimeOptionsWhenParsing): DateT
|
|||||||
return parsed || (moment() as DateTime);
|
return parsed || (moment() as DateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseOthers(value, options);
|
const timeZone = getTimeZone(options);
|
||||||
|
const zone = moment.tz.zone(timeZone);
|
||||||
|
const format = options?.format ?? systemDateFormats.fullDate;
|
||||||
|
|
||||||
|
if (zone && zone.name) {
|
||||||
|
return moment.tz(value, format, zone.name) as DateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (lowerCase(timeZone)) {
|
||||||
|
case 'utc':
|
||||||
|
return moment.utc(value, format) as DateTime;
|
||||||
|
default:
|
||||||
|
return moment(value, format) as DateTime;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseOthers = (value: DateTimeInput, options?: DateTimeOptionsWhenParsing): DateTime => {
|
const parseOthers = (value: DateTimeInput, options?: DateTimeOptionsWhenParsing): DateTime => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { DataSourceInstanceSettings } from './datasource';
|
import { DataSourceInstanceSettings } from './datasource';
|
||||||
import { PanelPluginMeta } from './panel';
|
import { PanelPluginMeta } from './panel';
|
||||||
import { GrafanaTheme } from './theme';
|
import { GrafanaTheme } from './theme';
|
||||||
|
import { SystemDateFormatSettings } from '../datetime';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the build information that will be available via the Grafana configuration.
|
* Describes the build information that will be available via the Grafana configuration.
|
||||||
@ -100,4 +101,5 @@ export interface GrafanaConfig {
|
|||||||
featureToggles: FeatureToggles;
|
featureToggles: FeatureToggles;
|
||||||
licenseInfo: LicenseInfo;
|
licenseInfo: LicenseInfo;
|
||||||
http2Enabled: boolean;
|
http2Enabled: boolean;
|
||||||
|
dateFormats?: SystemDateFormatSettings;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
dateTimeAsIsoNoDateIfToday,
|
dateTimeAsIsoNoDateIfToday,
|
||||||
dateTimeAsUS,
|
dateTimeAsUS,
|
||||||
dateTimeAsUSNoDateIfToday,
|
dateTimeAsUSNoDateIfToday,
|
||||||
|
dateTimeAsLocal,
|
||||||
dateTimeFromNow,
|
dateTimeFromNow,
|
||||||
toClockMilliseconds,
|
toClockMilliseconds,
|
||||||
toClockSeconds,
|
toClockSeconds,
|
||||||
@ -175,6 +176,7 @@ export const getCategories = (): ValueFormatCategory[] => [
|
|||||||
{ name: 'Datetime ISO (No date if today)', id: 'dateTimeAsIsoNoDateIfToday', fn: dateTimeAsIsoNoDateIfToday },
|
{ name: 'Datetime ISO (No date if today)', id: 'dateTimeAsIsoNoDateIfToday', fn: dateTimeAsIsoNoDateIfToday },
|
||||||
{ name: 'Datetime US', id: 'dateTimeAsUS', fn: dateTimeAsUS },
|
{ name: 'Datetime US', id: 'dateTimeAsUS', fn: dateTimeAsUS },
|
||||||
{ name: 'Datetime US (No date if today)', id: 'dateTimeAsUSNoDateIfToday', fn: dateTimeAsUSNoDateIfToday },
|
{ name: 'Datetime US (No date if today)', id: 'dateTimeAsUSNoDateIfToday', fn: dateTimeAsUSNoDateIfToday },
|
||||||
|
{ name: 'Datetime local', id: 'dateTimeAsLocal', fn: dateTimeAsLocal },
|
||||||
{ name: 'From Now', id: 'dateTimeFromNow', fn: dateTimeFromNow },
|
{ name: 'From Now', id: 'dateTimeFromNow', fn: dateTimeFromNow },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -3,7 +3,7 @@ import { toDuration as duration, toUtc, dateTime } from '../datetime/moment_wrap
|
|||||||
import { toFixed, toFixedScaled, FormattedValue, ValueFormatter } from './valueFormats';
|
import { toFixed, toFixedScaled, FormattedValue, ValueFormatter } from './valueFormats';
|
||||||
import { DecimalCount } from '../types/displayValue';
|
import { DecimalCount } from '../types/displayValue';
|
||||||
import { TimeZone } from '../types';
|
import { TimeZone } from '../types';
|
||||||
import { dateTimeFormat, dateTimeFormatTimeAgo } from '../datetime';
|
import { dateTimeFormat, dateTimeFormatTimeAgo, localTimeFormat } from '../datetime';
|
||||||
|
|
||||||
interface IntervalsInSeconds {
|
interface IntervalsInSeconds {
|
||||||
[interval: string]: number;
|
[interval: string]: number;
|
||||||
@ -369,6 +369,16 @@ export const dateTimeAsIso = toDateTimeValueFormatter('YYYY-MM-DD HH:mm:ss');
|
|||||||
export const dateTimeAsIsoNoDateIfToday = toDateTimeValueFormatter('YYYY-MM-DD HH:mm:ss', 'HH:mm:ss');
|
export const dateTimeAsIsoNoDateIfToday = toDateTimeValueFormatter('YYYY-MM-DD HH:mm:ss', 'HH:mm:ss');
|
||||||
export const dateTimeAsUS = toDateTimeValueFormatter('MM/DD/YYYY h:mm:ss a');
|
export const dateTimeAsUS = toDateTimeValueFormatter('MM/DD/YYYY h:mm:ss a');
|
||||||
export const dateTimeAsUSNoDateIfToday = toDateTimeValueFormatter('MM/DD/YYYY h:mm:ss a', 'h:mm:ss a');
|
export const dateTimeAsUSNoDateIfToday = toDateTimeValueFormatter('MM/DD/YYYY h:mm:ss a', 'h:mm:ss a');
|
||||||
|
export const dateTimeAsLocal = toDateTimeValueFormatter(
|
||||||
|
localTimeFormat({
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export function dateTimeFromNow(
|
export function dateTimeFromNow(
|
||||||
value: number,
|
value: number,
|
||||||
|
@ -9,6 +9,8 @@ import {
|
|||||||
GrafanaThemeType,
|
GrafanaThemeType,
|
||||||
LicenseInfo,
|
LicenseInfo,
|
||||||
PanelPluginMeta,
|
PanelPluginMeta,
|
||||||
|
systemDateFormats,
|
||||||
|
SystemDateFormatSettings,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
export class GrafanaBootConfig implements GrafanaConfig {
|
export class GrafanaBootConfig implements GrafanaConfig {
|
||||||
@ -59,6 +61,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
|||||||
licenseInfo: LicenseInfo = {} as LicenseInfo;
|
licenseInfo: LicenseInfo = {} as LicenseInfo;
|
||||||
rendererAvailable = false;
|
rendererAvailable = false;
|
||||||
http2Enabled = false;
|
http2Enabled = false;
|
||||||
|
dateFormats?: SystemDateFormatSettings;
|
||||||
|
|
||||||
constructor(options: GrafanaBootConfig) {
|
constructor(options: GrafanaBootConfig) {
|
||||||
this.theme = options.bootData.user.lightTheme ? getTheme(GrafanaThemeType.Light) : getTheme(GrafanaThemeType.Dark);
|
this.theme = options.bootData.user.lightTheme ? getTheme(GrafanaThemeType.Light) : getTheme(GrafanaThemeType.Dark);
|
||||||
@ -84,6 +87,10 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
|||||||
};
|
};
|
||||||
|
|
||||||
merge(this, defaults, options);
|
merge(this, defaults, options);
|
||||||
|
|
||||||
|
if (this.dateFormats) {
|
||||||
|
systemDateFormats.update(this.dateFormats);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
GrafanaThemeType,
|
GrafanaThemeType,
|
||||||
Field,
|
Field,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { getMultiSeriesGraphHoverInfo, findHoverIndexFromData } from './utils';
|
import { getMultiSeriesGraphHoverInfo, findHoverIndexFromData, graphTimeFormat } from './utils';
|
||||||
|
|
||||||
const mockResult = (
|
const mockResult = (
|
||||||
value: string,
|
value: string,
|
||||||
@ -196,4 +196,14 @@ describe('Graph utils', () => {
|
|||||||
expect(findHoverIndexFromData(timeField!, 300)).toBe(2);
|
expect(findHoverIndexFromData(timeField!, 300)).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('graphTimeFormat', () => {
|
||||||
|
it('graphTimeFormat', () => {
|
||||||
|
expect(graphTimeFormat(5, 1, 45 * 5 * 1000)).toBe('HH:mm:ss');
|
||||||
|
expect(graphTimeFormat(5, 1, 7200 * 5 * 1000)).toBe('HH:mm');
|
||||||
|
expect(graphTimeFormat(5, 1, 80000 * 5 * 1000)).toBe('MM/DD HH:mm');
|
||||||
|
expect(graphTimeFormat(5, 1, 2419200 * 5 * 1000)).toBe('MM/DD');
|
||||||
|
expect(graphTimeFormat(5, 1, 12419200 * 5 * 1000)).toBe('YYYY-MM');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
getFieldDisplayName,
|
getFieldDisplayName,
|
||||||
TimeZone,
|
TimeZone,
|
||||||
dateTimeFormat,
|
dateTimeFormat,
|
||||||
|
systemDateFormats,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -120,19 +121,22 @@ export const graphTimeFormat = (ticks: number | null, min: number | null, max: n
|
|||||||
const oneYear = 31536000000;
|
const oneYear = 31536000000;
|
||||||
|
|
||||||
if (secPerTick <= 45) {
|
if (secPerTick <= 45) {
|
||||||
return 'HH:mm:ss';
|
return systemDateFormats.interval.second;
|
||||||
}
|
}
|
||||||
if (secPerTick <= 7200 || range <= oneDay) {
|
if (secPerTick <= 7200 || range <= oneDay) {
|
||||||
return 'HH:mm';
|
return systemDateFormats.interval.minute;
|
||||||
}
|
}
|
||||||
if (secPerTick <= 80000) {
|
if (secPerTick <= 80000) {
|
||||||
return 'MM/DD HH:mm';
|
return systemDateFormats.interval.hour;
|
||||||
}
|
}
|
||||||
if (secPerTick <= 2419200 || range <= oneYear) {
|
if (secPerTick <= 2419200 || range <= oneYear) {
|
||||||
return 'MM/DD';
|
return systemDateFormats.interval.day;
|
||||||
}
|
}
|
||||||
return 'YYYY-MM';
|
if (secPerTick <= 31536000) {
|
||||||
|
return systemDateFormats.interval.month;
|
||||||
|
}
|
||||||
|
return systemDateFormats.interval.year;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'HH:mm';
|
return systemDateFormats.interval.minute;
|
||||||
};
|
};
|
||||||
|
@ -127,6 +127,12 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderTimeStamp(epochMs: number) {
|
||||||
|
return dateTimeFormat(epochMs, {
|
||||||
|
timeZone: this.props.timeZone,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderLogRow(
|
renderLogRow(
|
||||||
context?: LogRowContextRows,
|
context?: LogRowContextRows,
|
||||||
errors?: LogRowContextQueryErrors,
|
errors?: LogRowContextQueryErrors,
|
||||||
@ -143,7 +149,6 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
|||||||
allowDetails,
|
allowDetails,
|
||||||
row,
|
row,
|
||||||
showDuplicates,
|
showDuplicates,
|
||||||
timeZone,
|
|
||||||
showContextToggle,
|
showContextToggle,
|
||||||
showLabels,
|
showLabels,
|
||||||
showTime,
|
showTime,
|
||||||
@ -176,7 +181,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
|||||||
<Icon className={styles.topVerticalAlign} name={showDetails ? 'angle-down' : 'angle-right'} />
|
<Icon className={styles.topVerticalAlign} name={showDetails ? 'angle-down' : 'angle-right'} />
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
{showTime && <td className={style.logsRowLocalTime}>{dateTimeFormat(row.timeEpochMs, { timeZone })}</td>}
|
{showTime && <td className={style.logsRowLocalTime}>{this.renderTimeStamp(row.timeEpochMs)}</td>}
|
||||||
{showLabels && row.uniqueLabels && (
|
{showLabels && row.uniqueLabels && (
|
||||||
<td className={style.logsRowLabels}>
|
<td className={style.logsRowLabels}>
|
||||||
<LogLabels labels={row.uniqueLabels} />
|
<LogLabels labels={row.uniqueLabels} />
|
||||||
|
@ -126,7 +126,6 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
|||||||
logsRowLocalTime: css`
|
logsRowLocalTime: css`
|
||||||
label: logs-row__localtime;
|
label: logs-row__localtime;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
max-width: 12.5em;
|
|
||||||
`,
|
`,
|
||||||
logsRowLabels: css`
|
logsRowLabels: css`
|
||||||
label: logs-row__labels;
|
label: logs-row__labels;
|
||||||
|
@ -233,15 +233,15 @@ exports[`TimePickerContent renders recent absolute ranges correctly 1`] = `
|
|||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
||||||
"from": "2019-12-17T07:48:27Z",
|
"from": "2019-12-17 07:48:27",
|
||||||
"section": 3,
|
"section": 3,
|
||||||
"to": "2019-12-18T07:48:27Z",
|
"to": "2019-12-18 07:48:27",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
||||||
"from": "2019-10-17T07:48:27Z",
|
"from": "2019-10-17 07:48:27",
|
||||||
"section": 3,
|
"section": 3,
|
||||||
"to": "2019-10-18T07:48:27Z",
|
"to": "2019-10-18 07:48:27",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -297,15 +297,15 @@ exports[`TimePickerContent renders recent absolute ranges correctly 1`] = `
|
|||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
||||||
"from": "2019-12-17T07:48:27Z",
|
"from": "2019-12-17 07:48:27",
|
||||||
"section": 3,
|
"section": 3,
|
||||||
"to": "2019-12-18T07:48:27Z",
|
"to": "2019-12-18 07:48:27",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
||||||
"from": "2019-10-17T07:48:27Z",
|
"from": "2019-10-17 07:48:27",
|
||||||
"section": 3,
|
"section": 3,
|
||||||
"to": "2019-10-18T07:48:27Z",
|
"to": "2019-10-18 07:48:27",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { TimeOption, TimeRange, TimeZone, rangeUtil, dateTimeFormat, dateTimeFormatISO } from '@grafana/data';
|
import { TimeOption, TimeRange, TimeZone, rangeUtil, dateTimeFormat } from '@grafana/data';
|
||||||
|
|
||||||
export const mapOptionToTimeRange = (option: TimeOption, timeZone?: TimeZone): TimeRange => {
|
export const mapOptionToTimeRange = (option: TimeOption, timeZone?: TimeZone): TimeRange => {
|
||||||
return rangeUtil.convertRawToRange({ from: option.from, to: option.to }, timeZone);
|
return rangeUtil.convertRawToRange({ from: option.from, to: option.to }, timeZone);
|
||||||
@ -9,8 +9,8 @@ export const mapRangeToTimeOption = (range: TimeRange, timeZone?: TimeZone): Tim
|
|||||||
const to = dateTimeFormat(range.to, { timeZone });
|
const to = dateTimeFormat(range.to, { timeZone });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
from: dateTimeFormatISO(range.from, { timeZone }),
|
from,
|
||||||
to: dateTimeFormatISO(range.to, { timeZone }),
|
to,
|
||||||
section: 3,
|
section: 3,
|
||||||
display: `${from} to ${to}`,
|
display: `${from} to ${to}`,
|
||||||
};
|
};
|
||||||
|
@ -54,6 +54,8 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settings["dateFormats"] = hs.Cfg.DateFormats
|
||||||
|
|
||||||
var data = dtos.IndexViewData{
|
var data = dtos.IndexViewData{
|
||||||
User: &dtos.CurrentUser{
|
User: &dtos.CurrentUser{
|
||||||
Id: c.UserId,
|
Id: c.UserId,
|
||||||
|
@ -19,6 +19,7 @@ func init() {
|
|||||||
func GetPreferencesWithDefaults(query *models.GetPreferencesWithDefaultsQuery) error {
|
func GetPreferencesWithDefaults(query *models.GetPreferencesWithDefaultsQuery) error {
|
||||||
params := make([]interface{}, 0)
|
params := make([]interface{}, 0)
|
||||||
filter := ""
|
filter := ""
|
||||||
|
|
||||||
if len(query.User.Teams) > 0 {
|
if len(query.User.Teams) > 0 {
|
||||||
filter = "(org_id=? AND team_id IN (?" + strings.Repeat(",?", len(query.User.Teams)-1) + ")) OR "
|
filter = "(org_id=? AND team_id IN (?" + strings.Repeat(",?", len(query.User.Teams)-1) + ")) OR "
|
||||||
params = append(params, query.User.OrgId)
|
params = append(params, query.User.OrgId)
|
||||||
@ -26,6 +27,7 @@ func GetPreferencesWithDefaults(query *models.GetPreferencesWithDefaultsQuery) e
|
|||||||
params = append(params, v)
|
params = append(params, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filter += "(org_id=? AND user_id=? AND team_id=0) OR (org_id=? AND team_id=0 AND user_id=0)"
|
filter += "(org_id=? AND user_id=? AND team_id=0) OR (org_id=? AND team_id=0 AND user_id=0)"
|
||||||
params = append(params, query.User.OrgId)
|
params = append(params, query.User.OrgId)
|
||||||
params = append(params, query.User.UserId)
|
params = append(params, query.User.UserId)
|
||||||
|
28
pkg/setting/date_formats.go
Normal file
28
pkg/setting/date_formats.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package setting
|
||||||
|
|
||||||
|
type DateFormats struct {
|
||||||
|
FullDate string `json:"fullDate"`
|
||||||
|
UseBrowserLocale bool `json:"useBrowserLocale"`
|
||||||
|
Interval DateFormatIntervals `json:"interval"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DateFormatIntervals struct {
|
||||||
|
Second string `json:"second"`
|
||||||
|
Minute string `json:"minute"`
|
||||||
|
Hour string `json:"hour"`
|
||||||
|
Day string `json:"day"`
|
||||||
|
Month string `json:"month"`
|
||||||
|
Year string `json:"year"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Cfg) readDateFormats() {
|
||||||
|
dateFormats := cfg.Raw.Section("date_formats")
|
||||||
|
cfg.DateFormats.FullDate, _ = valueAsString(dateFormats, "full_date", "YYYY-MM-DD HH:mm:ss")
|
||||||
|
cfg.DateFormats.Interval.Second, _ = valueAsString(dateFormats, "interval_second", "HH:mm:ss")
|
||||||
|
cfg.DateFormats.Interval.Minute, _ = valueAsString(dateFormats, "interval_minute", "HH:mm")
|
||||||
|
cfg.DateFormats.Interval.Hour, _ = valueAsString(dateFormats, "interval_hour", "MM-DD HH:mm")
|
||||||
|
cfg.DateFormats.Interval.Day, _ = valueAsString(dateFormats, "interval_day", "YYYY-MM-DD")
|
||||||
|
cfg.DateFormats.Interval.Month, _ = valueAsString(dateFormats, "interval_month", "YYYY-MM")
|
||||||
|
cfg.DateFormats.Interval.Year = "YYYY"
|
||||||
|
cfg.DateFormats.UseBrowserLocale = dateFormats.Key("date_format_use_browser_locale").MustBool(false)
|
||||||
|
}
|
@ -300,10 +300,11 @@ type Cfg struct {
|
|||||||
ApiKeyMaxSecondsToLive int64
|
ApiKeyMaxSecondsToLive int64
|
||||||
|
|
||||||
// Use to enable new features which may still be in alpha/beta stage.
|
// Use to enable new features which may still be in alpha/beta stage.
|
||||||
FeatureToggles map[string]bool
|
FeatureToggles map[string]bool
|
||||||
|
|
||||||
AnonymousHideVersion bool
|
AnonymousHideVersion bool
|
||||||
|
|
||||||
|
DateFormats DateFormats
|
||||||
|
|
||||||
// Annotations
|
// Annotations
|
||||||
AlertingAnnotationCleanupSetting AnnotationCleanupSettings
|
AlertingAnnotationCleanupSetting AnnotationCleanupSettings
|
||||||
DashboardAnnotationCleanupSettings AnnotationCleanupSettings
|
DashboardAnnotationCleanupSettings AnnotationCleanupSettings
|
||||||
@ -835,6 +836,8 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
|||||||
ConnStr: connStr,
|
ConnStr: connStr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg.readDateFormats()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { appEvents } from 'app/core/core';
|
import { appEvents } from 'app/core/core';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
import { textUtil } from '@grafana/data';
|
import { textUtil, systemDateFormats } from '@grafana/data';
|
||||||
|
|
||||||
export default function GraphTooltip(this: any, elem: any, dashboard: any, scope: any, getSeriesFn: any) {
|
export default function GraphTooltip(this: any, elem: any, dashboard: any, scope: any, getSeriesFn: any) {
|
||||||
const self = this;
|
const self = this;
|
||||||
@ -233,9 +233,9 @@ export default function GraphTooltip(this: any, elem: any, dashboard: any, scope
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (seriesList[0].hasMsResolution) {
|
if (seriesList[0].hasMsResolution) {
|
||||||
tooltipFormat = 'YYYY-MM-DD HH:mm:ss.SSS';
|
tooltipFormat = systemDateFormats.fullDateMS;
|
||||||
} else {
|
} else {
|
||||||
tooltipFormat = 'YYYY-MM-DD HH:mm:ss';
|
tooltipFormat = systemDateFormats.fullDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allSeriesMode) {
|
if (allSeriesMode) {
|
||||||
|
@ -12,8 +12,7 @@ import {
|
|||||||
getSeriesTimeStep,
|
getSeriesTimeStep,
|
||||||
TimeZone,
|
TimeZone,
|
||||||
hasMsResolution,
|
hasMsResolution,
|
||||||
MS_DATE_TIME_FORMAT,
|
systemDateFormats,
|
||||||
DEFAULT_DATE_TIME_FORMAT,
|
|
||||||
FieldColor,
|
FieldColor,
|
||||||
FieldColorMode,
|
FieldColorMode,
|
||||||
FieldConfigSource,
|
FieldConfigSource,
|
||||||
@ -117,7 +116,7 @@ export const getGraphSeriesModel = (
|
|||||||
...timeField,
|
...timeField,
|
||||||
type: timeField.type,
|
type: timeField.type,
|
||||||
config: {
|
config: {
|
||||||
unit: `time:${useMsDateFormat ? MS_DATE_TIME_FORMAT : DEFAULT_DATE_TIME_FORMAT}`,
|
unit: systemDateFormats.getTimeFieldUnit(useMsDateFormat),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
formattedValueToString,
|
formattedValueToString,
|
||||||
dateTimeFormat,
|
dateTimeFormat,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
import { graphTimeFormat } from '@grafana/ui';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
|
|
||||||
const MIN_CARD_SIZE = 1,
|
const MIN_CARD_SIZE = 1,
|
||||||
@ -155,9 +156,13 @@ export class HeatmapRenderer {
|
|||||||
.range([0, this.chartWidth]);
|
.range([0, this.chartWidth]);
|
||||||
|
|
||||||
const ticks = this.chartWidth / DEFAULT_X_TICK_SIZE_PX;
|
const ticks = this.chartWidth / DEFAULT_X_TICK_SIZE_PX;
|
||||||
const format = ticksUtils.grafanaTimeFormat(ticks, this.timeRange.from, this.timeRange.to);
|
const format = graphTimeFormat(ticks, this.timeRange.from.valueOf(), this.timeRange.to.valueOf());
|
||||||
const timeZone = this.ctrl.dashboard.getTimezone();
|
const timeZone = this.ctrl.dashboard.getTimezone();
|
||||||
const formatter = (date: Date) => dateTimeFormat(date, { format, timeZone });
|
const formatter = (date: Date) =>
|
||||||
|
dateTimeFormat(date.valueOf(), {
|
||||||
|
format: format,
|
||||||
|
timeZone: timeZone,
|
||||||
|
});
|
||||||
|
|
||||||
const xAxis = d3
|
const xAxis = d3
|
||||||
.axisBottom(this.xScale)
|
.axisBottom(this.xScale)
|
||||||
|
Loading…
Reference in New Issue
Block a user