Skip to content
Snippets Groups Projects
Commit d90f2311 authored by Tom Robinson's avatar Tom Robinson
Browse files

Refactor date/time formatting

parent b55b1d87
No related branches found
No related tags found
No related merge requests found
......@@ -25,6 +25,13 @@ import { rangeForValue } from "metabase/lib/dataset";
import { getFriendlyName } from "metabase/visualizations/lib/utils";
import { decimalCount } from "metabase/visualizations/lib/numeric";
import {
DEFAULT_DATE_STYLE,
getDateFormatFromStyle,
DEFAULT_TIME_STYLE,
getTimeFormatFromStyle,
} from "metabase/lib/formatting/date";
import Field from "metabase-lib/lib/metadata/Field";
import type { Column, Value } from "metabase/meta/types/Dataset";
import type { DatetimeUnit } from "metabase/meta/types/Query";
......@@ -284,27 +291,41 @@ function formatWeek(m: Moment, options: FormattingOptions = {}) {
return formatMajorMinor(m.format("wo"), m.format("gggg"), options);
}
function replaceDateFormatNames(format, options) {
return format
.replace(/\bMMMM\b/g, getMonthFormat(options))
.replace(/\bdddd\b/g, getDayFormat(options));
}
function formatDateTimeWithFormats(value, dateFormat, timeFormat, options) {
let m = parseTimestamp(value, options.column && options.column.unit);
if (!m.isValid()) {
return String(value);
}
const format = [];
if (dateFormat) {
format.push(replaceDateFormatNames(dateFormat, options));
}
if (timeFormat && options.time_enabled) {
format.push(timeFormat);
}
return m.format(format.join(" "));
}
function formatDateTime(value, options) {
let m = parseTimestamp(value, options.column && options.column.unit);
if (!m.isValid()) {
return String(value);
}
if (options.date_format) {
const format = [];
if (options.date_abbreviate) {
format.push(
options.date_format
.replace(/\bMMMM\b/g, getMonthFormat(options))
.replace(/\bdddd\b/g, getDayFormat(options)),
);
} else {
format.push(options.date_format);
}
if (options.time_format && options.time_enabled !== false) {
format.push(options.time_format);
}
return m.format(format.join(" "));
if (options.date_format || options.time_format) {
formatDateTimeWithFormats(
value,
options.date_format,
options.time_format,
options,
);
} else {
if (options.show_time === false) {
return m.format(options.date_abbreviate ? "ll" : "LL");
......@@ -324,71 +345,31 @@ export function formatDateTimeWithUnit(
return String(value);
}
// only use custom formats for unbucketed dates for now
if (options.date_format) {
formatDateTime(value, options);
// expand "week" into a range in specific contexts
if (unit === "week") {
if (
(options.type === "tooltip" || options.type === "cell") &&
!options.noRange
) {
// tooltip show range like "January 1 - 7, 2017"
return formatDateTimeRangeWithUnit(value, unit, options);
}
}
switch (unit) {
case "hour": // 12 AM - January 1, 2015
return formatMajorMinor(
m.format("h A"),
m.format(`${getMonthFormat(options)} D, YYYY`),
options,
);
case "day": // January 1, 2015
return m.format(`${getMonthFormat(options)} D, YYYY`);
case "week": // 1st - 2015
if (options.type === "tooltip" && !options.noRange) {
// tooltip show range like "January 1 - 7, 2017"
return formatDateTimeRangeWithUnit(value, unit, options);
} else if (options.type === "cell" && !options.noRange) {
// table cells show range like "Jan 1, 2017 - Jan 7, 2017"
return formatDateTimeRangeWithUnit(value, unit, options);
} else if (options.type === "axis") {
// axis ticks show start of the week as "Jan 1"
return m
.clone()
.startOf(unit)
.format(`MMM D`);
} else {
return formatWeek(m, options);
}
case "month": // January 2015
return options.jsx ? (
<div>
<span className="text-bold">{m.format(getMonthFormat(options))}</span>{" "}
{m.format("YYYY")}
</div>
) : (
m.format(`${getMonthFormat(options)} YYYY`)
);
case "year": // 2015
return m.format("YYYY");
case "quarter": // Q1 - 2015
return formatMajorMinor(m.format("[Q]Q"), m.format("YYYY"), {
...options,
majorWidth: 0,
});
case "minute-of-hour":
return m.format("m");
case "hour-of-day": // 12 AM
return m.format("h A");
case "day-of-week": // Sunday
return m.format(getDayFormat(options));
case "day-of-month":
return m.format("D");
case "day-of-year":
return m.format("DDD");
case "week-of-year": // 1st
return m.format("wo");
case "month-of-year": // January
return m.format(getMonthFormat(options));
case "quarter-of-year": // January
return m.format("[Q]Q");
default:
return formatDateTime(value, options);
let dateFormat = options.date_format;
let timeFormat = options.time_format;
if (!dateFormat) {
const dateStyle = options.date_style || DEFAULT_DATE_STYLE;
dateFormat = getDateFormatFromStyle(dateStyle, unit);
}
if (!timeFormat) {
const timeStyle = options.time_style || DEFAULT_TIME_STYLE;
timeFormat = getTimeFormatFromStyle(timeStyle, unit, options.time_enabled);
}
return formatDateTimeWithFormats(value, dateFormat, timeFormat, options);
}
export function formatTime(value: Value) {
......
const DEFAULT_DATE_FORMATS = {
year: "YYYY",
quarter: "[Q]Q - YYYY",
"minute-of-hour": "m",
"hour-of-day": "h A",
"day-of-week": "dddd",
"day-of-month": "D",
"day-of-year": "DDD",
"week-of-year": "wo",
"month-of-year": "MMMM",
"quarter-of-year": "[Q]Q",
};
// a "date style" is essentially a "day" format with overrides for larger units
const DATE_STYLE_TO_FORMAT = {
"M/D/YY": {
month: "M/YY",
},
"D/M/YY": {
month: "M/YY",
},
"YYYY/M/D": {
month: "YYYY/M",
quarter: "YYYY - [Q]Q",
},
"MMMM D, YYYY": {
month: "MMMM, YYYY",
},
"D MMMM, YYYY": {
month: "MMMM, YYYY",
},
"dddd, MMMM D, YYYY": {
week: "MMMM D, YYYY",
month: "MMMM, YYYY",
},
};
export const DEFAULT_DATE_STYLE = "MMMM D, YYYY";
export function getDateFormatFromStyle(style, unit) {
if (DATE_STYLE_TO_FORMAT[style]) {
if (DATE_STYLE_TO_FORMAT[style][unit]) {
return DATE_STYLE_TO_FORMAT[style][unit];
}
} else {
console.warn("Unknown date style", style);
}
if (DEFAULT_DATE_FORMATS[unit]) {
return DEFAULT_DATE_FORMATS[unit];
}
return style;
}
const UNITS_WITH_HOUR = [null, "default", "second", "minute", "hour"];
const UNITS_WITH_DAY = [...UNITS_WITH_HOUR, "day", "week"];
const UNITS_WITH_HOUR_SET = new Set(UNITS_WITH_HOUR);
const UNITS_WITH_DAY_SET = new Set(UNITS_WITH_DAY);
export const hasHour = unit => UNITS_WITH_HOUR_SET.has(unit);
export const hasDay = unit => UNITS_WITH_DAY_SET.has(unit);
export const DEFAULT_TIME_STYLE = "h:mm A";
export function getTimeFormatFromStyle(style, unit, timeEnabled) {
let format = style;
if (!timeEnabled || timeEnabled === "milliseconds") {
return format.replace(/mm/, "mm:ss.SSS");
} else if (timeEnabled === "seconds") {
return format.replace(/mm/, "mm:ss");
} else {
return format;
}
}
......@@ -8,6 +8,12 @@ import { keyForColumn } from "metabase/lib/dataset";
import { isDate, isNumber, isCoordinate } from "metabase/lib/schema_metadata";
import { getVisualizationRaw } from "metabase/visualizations";
import { numberFormatterForOptions } from "metabase/lib/formatting";
import {
DEFAULT_DATE_STYLE,
getDateFormatFromStyle,
hasDay,
hasHour,
} from "metabase/lib/formatting/date";
const DEFAULT_GET_COLUMNS = (series, vizSettings) =>
[].concat(...series.map(s => s.data.cols));
......@@ -31,51 +37,99 @@ export function columnSettings({
const EXAMPLE_DATE = moment("2018-01-07 17:24");
function dateTimeFormatOption(format, description) {
function getDateStyleOptionsForUnit(unit) {
const options = [
dateStyleOption("M/D/YY", unit, hasDay(unit) && "month, day, year"),
dateStyleOption("D/M/YY", unit, hasDay(unit) && "day, month, year"),
dateStyleOption("YYYY/M/D", unit, hasDay(unit) && "year, month, day"),
dateStyleOption("MMMM D, YYYY", unit),
dateStyleOption("D MMMM, YYYY", unit),
dateStyleOption("dddd, MMMM D, YYYY", unit),
];
const seen = new Set();
return options.filter(option => {
const format = getDateFormatFromStyle(option.value, unit);
if (seen.has(format)) {
return false;
} else {
seen.add(format);
return true;
}
});
}
function dateStyleOption(style, unit, description) {
const format = getDateFormatFromStyle(style, unit);
return {
name:
EXAMPLE_DATE.format(format) + (description ? ` (${description})` : ``),
value: style,
};
}
function timeStyleOption(style, description) {
const format = style;
return {
name:
EXAMPLE_DATE.format(format) + (description ? ` (${description})` : ``),
value: format,
value: style,
};
}
export const DATE_COLUMN_SETTINGS = {
date_format: {
date_style: {
title: t`Date style`,
widget: "radio",
default: "dddd, MMMM D, YYYY",
props: {
options: [
dateTimeFormatOption("M/D/YYYY", "month, day, year"),
dateTimeFormatOption("D/M/YYYY", "day, month, year"),
dateTimeFormatOption("YYYY/M/D", "year, month, day"),
dateTimeFormatOption("MMMM D, YYYY"),
dateTimeFormatOption("D MMMM YYYY"),
dateTimeFormatOption("dddd, MMMM D, YYYY"),
],
},
default: DEFAULT_DATE_STYLE,
getProps: ({ unit }) => ({
options: getDateStyleOptionsForUnit(unit),
}),
getHidden: ({ unit }) => getDateStyleOptionsForUnit(unit).length < 2,
},
date_abbreviate: {
title: t`Abbreviate names of days and months`,
widget: "toggle",
default: false,
getHidden: ({ unit }, settings) => {
const format = getDateFormatFromStyle(settings["date_style"], unit);
return !format.match(/MMMM|dddd/);
},
readDependencies: ["date_style"],
},
time_enabled: {
title: t`Show the time`,
widget: "toggle",
default: true,
widget: "buttonGroup",
getProps: ({ unit }, settings) => {
const options = [
{ name: t`Off`, value: null },
{ name: t`Minutes`, value: "minutes" },
];
if (!unit || unit === "default" || unit === "second") {
options.push({ name: t`Seconds`, value: "seconds" });
}
if (!unit || unit === "default") {
options.push({ name: t`Milliseconds`, value: "milliseconds" });
}
if (options.length === 2) {
options[1].name = t`On`;
}
return { options };
},
getHidden: ({ unit }, settings) => !hasHour(unit),
getDefault: ({ unit }) => (hasHour(unit) ? "minutes" : null),
},
time_format: {
time_style: {
title: t`Time style`,
widget: "radio",
default: "h:mm A",
props: {
getProps: (column, settings) => ({
options: [
dateTimeFormatOption("h:mm A", "12-hour clock"),
dateTimeFormatOption("k:mm", "24-hour clock"),
timeStyleOption("h:mm A", "12-hour clock"),
timeStyleOption("k:mm", "24-hour clock"),
],
},
}),
getHidden: (column, settings) => !settings["time_enabled"],
readDependencies: ["time_enabled"],
},
};
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment