diff --git a/frontend/src/metabase/lib/formatting.js b/frontend/src/metabase/lib/formatting.js index dae8a3b243671318bd98242881891debd51e24a6..9cec63dc54e99bc0b21a27f2f0b7d3d4f9ecebee 100644 --- a/frontend/src/metabase/lib/formatting.js +++ b/frontend/src/metabase/lib/formatting.js @@ -4,7 +4,7 @@ import inflection from "inflection"; import moment from "moment-timezone"; import Humanize from "humanize-plus"; import React from "react"; -import { ngettext, msgid } from "ttag"; +import { msgid, ngettext } from "ttag"; import Mustache from "mustache"; import ReactMarkdown from "react-markdown"; @@ -12,29 +12,34 @@ import ReactMarkdown from "react-markdown"; import ExternalLink from "metabase/components/ExternalLink"; import { - isDate, - isNumber, isCoordinate, + isDate, + isEmail, isLatitude, isLongitude, + isNumber, isTime, isURL, - isEmail, } from "metabase/lib/schema_metadata"; -import { parseTimestamp, parseTime } from "metabase/lib/time"; +import { parseTime, parseTimestamp } from "metabase/lib/time"; import { rangeForValue } from "metabase/lib/dataset"; import { getFriendlyName } from "metabase/visualizations/lib/utils"; import { decimalCount } from "metabase/visualizations/lib/numeric"; import { - getDataFromClicked, clickBehaviorIsValid, + getDataFromClicked, } from "metabase/lib/click-behavior"; +import type { + DateStyle, + TimeEnabled, + TimeStyle, +} from "metabase/lib/formatting/date"; import { DEFAULT_DATE_STYLE, - getDateFormatFromStyle, DEFAULT_TIME_STYLE, + getDateFormatFromStyle, getTimeFormatFromStyle, hasHour, } from "metabase/lib/formatting/date"; @@ -42,18 +47,12 @@ import { renderLinkTextForClick, renderLinkURLForClick, } from "metabase/lib/formatting/link"; -import { NULL_NUMERIC_VALUE, NULL_DISPLAY_VALUE } from "metabase/lib/constants"; +import { NULL_DISPLAY_VALUE, NULL_NUMERIC_VALUE } from "metabase/lib/constants"; import type Field from "metabase-lib/lib/metadata/Field"; import type { Column, Value } from "metabase-types/types/Dataset"; import type { DatetimeUnit } from "metabase-types/types/Query"; import type { Moment } from "metabase-types/types"; - -import type { - DateStyle, - TimeStyle, - TimeEnabled, -} from "metabase/lib/formatting/date"; import type { ClickObject } from "metabase-types/types/Visualization"; // a one or two character string specifying the decimal and grouping separator characters @@ -545,6 +544,33 @@ export function formatTime(value: Value) { } } +export function formatTimeWithUnit( + value: Value, + unit: DatetimeUnit, + options: FormattingOptions = {}, +) { + const m = parseTimestamp(value, unit, options.local); + if (!m.isValid()) { + return String(value); + } + + const timeStyle = options.time_style + ? options.time_style + : DEFAULT_TIME_STYLE; + + const timeEnabled = options.time_enabled + ? options.time_enabled + : hasHour(unit) + ? "minutes" + : null; + + const timeFormat = options.time_format + ? options.time_format + : getTimeFormatFromStyle(timeStyle, unit, timeEnabled); + + return m.format(timeFormat); +} + // https://github.com/angular/angular.js/blob/v1.6.3/src/ng/directive/input.js#L27 const EMAIL_ALLOW_LIST_REGEX = /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/; diff --git a/frontend/src/metabase/lib/time.js b/frontend/src/metabase/lib/time.js index 0fc903bbaecd5375acc51152d2b7f8a6800b19ba..25bbd18c93d3a3a3e6d5598c3014512dd9346c93 100644 --- a/frontend/src/metabase/lib/time.js +++ b/frontend/src/metabase/lib/time.js @@ -105,6 +105,7 @@ export function parseTime(value) { } } +// @deprecated - use formatTimeWithUnit(hour, "hour-of-day") export function formatHourAMPM(hour) { if (hour > 12) { const newHour = hour - 12; diff --git a/frontend/test/metabase/lib/formatting.unit.spec.js b/frontend/test/metabase/lib/formatting.unit.spec.js index dd44eb85b058f51acb19e5cf28f13d2d69080ea9..a34ceba10f1f9295b0cf023fbcafe29254e75153 100644 --- a/frontend/test/metabase/lib/formatting.unit.spec.js +++ b/frontend/test/metabase/lib/formatting.unit.spec.js @@ -6,6 +6,7 @@ import { formatValue, formatUrl, formatDateTimeWithUnit, + formatTimeWithUnit, slugify, } from "metabase/lib/formatting"; import ExternalLink from "metabase/components/ExternalLink"; @@ -468,6 +469,47 @@ describe("formatting", () => { }); }); + describe("formatTimeWithUnit", () => { + it("should format hour-of day with default options", () => { + expect(formatTimeWithUnit(8, "hour-of-day")).toEqual("8:00 AM"); + }); + + it("should format hour-of-day with 12 hour clock", () => { + const options = { + time_style: "h:mm A", + }; + + expect(formatTimeWithUnit(14, "hour-of-day", options)).toEqual("2:00 PM"); + }); + + it("should format hour-of-day with 24 hour clock", () => { + const options = { + time_style: "HH:mm", + }; + + expect(formatTimeWithUnit(14, "hour-of-day", options)).toEqual("14:00"); + }); + + it("should format hour-of-day with custom precision", () => { + const options = { + time_style: "HH:mm", + time_enabled: "seconds", + }; + + expect(formatTimeWithUnit(14.4, "hour-of-day", options)).toEqual( + "14:00:00", + ); + }); + + it("should format hour-of-day with a custom format", () => { + const options = { + time_format: "HH", + }; + + expect(formatTimeWithUnit(14.4, "hour-of-day", options)).toEqual("14"); + }); + }); + describe("slugify", () => { it("should slugify Chinese", () => { expect(slugify("é¡žåž‹")).toEqual("%E9%A1%9E%E5%9E%8B");