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");