diff --git a/assets/js/components/GlobalSettings/UserInterfaceSettings.vue b/assets/js/components/GlobalSettings/UserInterfaceSettings.vue
index df6746b23..2ccaaea23 100644
--- a/assets/js/components/GlobalSettings/UserInterfaceSettings.vue
+++ b/assets/js/components/GlobalSettings/UserInterfaceSettings.vue
@@ -42,6 +42,21 @@
equal-width
/>
+
+
+
@@ -82,12 +97,15 @@ import FormRow from "../Helper/FormRow.vue";
import SelectGroup from "../Helper/SelectGroup.vue";
import { getLocalePreference, setLocalePreference, LOCALES, removeLocalePreference } from "@/i18n";
import { getThemePreference, setThemePreference, THEMES } from "@/theme";
-import { getUnits, setUnits, UNITS } from "@/units";
+import { getUnits, setUnits, UNITS, is12hFormat, set12hFormat } from "@/units";
import { getHiddenFeatures, setHiddenFeatures } from "@/featureflags";
import { isApp } from "@/utils/native";
import { defineComponent, type PropType } from "vue";
import type { Sponsor } from "@/types/evcc";
+const TIME_12H = "12";
+const TIME_24H = "24";
+
export default defineComponent({
name: "UserInterfaceSettings",
components: { TelemetrySettings, FormRow, SelectGroup },
@@ -99,10 +117,12 @@ export default defineComponent({
theme: getThemePreference(),
language: getLocalePreference() || "",
unit: getUnits(),
+ timeFormat: is12hFormat() ? TIME_12H : TIME_24H,
hiddenFeatures: getHiddenFeatures(),
fullscreenActive: false,
THEMES,
UNITS,
+ TIME_FORMATS: [TIME_24H, TIME_12H],
};
},
computed: {
@@ -126,6 +146,9 @@ export default defineComponent({
unit(value) {
setUnits(value);
},
+ timeFormat(value) {
+ set12hFormat(value === TIME_12H);
+ },
theme(value) {
setThemePreference(value);
},
diff --git a/assets/js/components/Tariff/TariffChart.vue b/assets/js/components/Tariff/TariffChart.vue
index f272be97f..0e626351c 100644
--- a/assets/js/components/Tariff/TariffChart.vue
+++ b/assets/js/components/Tariff/TariffChart.vue
@@ -27,7 +27,7 @@
{{
- slot.startHour
+ formatHour(slot.startHour)
}}
{{ slot.day }}
@@ -55,6 +55,7 @@
diff --git a/assets/js/mixins/formatter.test.js b/assets/js/mixins/formatter.test.js
index d33b6fb89..4c82163df 100644
--- a/assets/js/mixins/formatter.test.js
+++ b/assets/js/mixins/formatter.test.js
@@ -1,6 +1,9 @@
import { mount, config } from "@vue/test-utils";
-import { describe, expect, test } from "vitest";
+import { describe, expect, test, vi } from "vitest";
import formatter, { POWER_UNIT } from "./formatter";
+import * as units from "../units";
+
+const is12hSpy = vi.spyOn(units, "is12hFormat").mockReturnValue(false);
config.global.mocks["$i18n"] = { locale: "de-DE" };
config.global.mocks["$t"] = (a) => a;
@@ -183,3 +186,23 @@ describe("fmtPercentage", () => {
expect(fmt.fmtPercentage(-100, 0, false)).eq("-100 %");
});
});
+
+const testDate = new Date(2023, 0, 15, 15, 30); // Jan 15, 2023, 15:30 (3:30 PM)
+
+describe("12h/24h time format", () => {
+ test("12h format", () => {
+ is12hSpy.mockReturnValue(true);
+ expect(fmt.fmtHourMinute(testDate)).toBe("3:30 PM");
+ expect(fmt.fmtFullDateTime(testDate)).toBe("So., 15. Jan., 3:30 PM");
+ expect(fmt.fmtWeekdayTime(testDate)).toBe("So. 3:30 PM");
+ expect(fmt.fmtAbsoluteDate(testDate)).toBe("So 3:30 PM");
+ });
+
+ test("24h format", () => {
+ is12hSpy.mockReturnValue(false);
+ expect(fmt.fmtHourMinute(testDate)).toBe("15:30");
+ expect(fmt.fmtFullDateTime(testDate)).toBe("So., 15. Jan., 15:30");
+ expect(fmt.fmtWeekdayTime(testDate)).toBe("So., 15:30");
+ expect(fmt.fmtAbsoluteDate(testDate)).toBe("So 15:30");
+ });
+});
diff --git a/assets/js/mixins/formatter.ts b/assets/js/mixins/formatter.ts
index 250f22738..75d263d5e 100644
--- a/assets/js/mixins/formatter.ts
+++ b/assets/js/mixins/formatter.ts
@@ -1,4 +1,5 @@
import { defineComponent } from "vue";
+import { is12hFormat } from "@/units";
import { CURRENCY } from "../types/evcc";
// list of currencies where energy price should be displayed in subunits (factor 100)
@@ -198,6 +199,7 @@ export default defineComponent({
if (locale === "de") return date.getHours();
return new Intl.DateTimeFormat(locale, {
hour: "numeric",
+ hour12: is12hFormat(),
}).format(date);
},
weekdayShort(date: Date) {
@@ -210,6 +212,7 @@ export default defineComponent({
const hour = new Intl.DateTimeFormat(this.$i18n?.locale, {
hour: "numeric",
minute: "numeric",
+ hour12: is12hFormat(),
}).format(date);
return `${weekday} ${hour}`.trim();
@@ -218,6 +221,7 @@ export default defineComponent({
return new Intl.DateTimeFormat(this.$i18n?.locale, {
hour: "numeric",
minute: "numeric",
+ hour12: is12hFormat(),
}).format(date);
},
fmtFullDateTime(date: Date, short: boolean) {
@@ -227,6 +231,7 @@ export default defineComponent({
day: "numeric",
hour: "numeric",
minute: "numeric",
+ hour12: is12hFormat(),
}).format(date);
},
fmtWeekdayTime(date: Date) {
@@ -234,6 +239,7 @@ export default defineComponent({
weekday: "short",
hour: "numeric",
minute: "numeric",
+ hour12: is12hFormat(),
}).format(date);
},
fmtMonthYear(date: Date) {
diff --git a/assets/js/settings.js b/assets/js/settings.js
index a64a86006..d3710d018 100644
--- a/assets/js/settings.js
+++ b/assets/js/settings.js
@@ -3,6 +3,7 @@ import { reactive, watch } from "vue";
const SETTINGS_LOCALE = "settings_locale";
const SETTINGS_THEME = "settings_theme";
const SETTINGS_UNIT = "settings_unit";
+const SETTINGS_12H_FORMAT = "settings_12h_format";
const SETTINGS_HIDDEN_FEATURES = "settings_hidden_features";
const SETTINGS_ENERGYFLOW_DETAILS = "settings_energyflow_details";
const SETTINGS_ENERGYFLOW_PV = "settings_energyflow_pv";
@@ -59,6 +60,7 @@ const settings = reactive({
locale: read(SETTINGS_LOCALE),
theme: read(SETTINGS_THEME),
unit: read(SETTINGS_UNIT),
+ is12hFormat: readBool(SETTINGS_12H_FORMAT),
hiddenFeatures: readBool(SETTINGS_HIDDEN_FEATURES),
energyflowDetails: readBool(SETTINGS_ENERGYFLOW_DETAILS),
energyflowPv: readBool(SETTINGS_ENERGYFLOW_PV),
@@ -76,6 +78,7 @@ const settings = reactive({
watch(() => settings.locale, save(SETTINGS_LOCALE));
watch(() => settings.theme, save(SETTINGS_THEME));
watch(() => settings.unit, save(SETTINGS_UNIT));
+watch(() => settings.is12hFormat, saveBool(SETTINGS_12H_FORMAT));
watch(() => settings.hiddenFeatures, saveBool(SETTINGS_HIDDEN_FEATURES));
watch(() => settings.energyflowDetails, saveBool(SETTINGS_ENERGYFLOW_DETAILS));
watch(() => settings.energyflowPv, saveBool(SETTINGS_ENERGYFLOW_PV));
diff --git a/assets/js/units.js b/assets/js/units.js
index ca11d5605..b763ca1fb 100644
--- a/assets/js/units.js
+++ b/assets/js/units.js
@@ -29,3 +29,11 @@ export function getUnits() {
export function setUnits(value) {
settings.unit = value;
}
+
+export function is12hFormat() {
+ return settings.is12hFormat;
+}
+
+export function set12hFormat(value) {
+ settings.is12hFormat = value;
+}
diff --git a/i18n/de.json b/i18n/de.json
index 0c11d54a9..336139a89 100644
--- a/i18n/de.json
+++ b/i18n/de.json
@@ -931,6 +931,11 @@
"label": "Design",
"light": "Hell"
},
+ "time": {
+ "12h": "12h",
+ "24h": "24h",
+ "label": "Zeitformat"
+ },
"title": "Darstellung",
"unit": {
"km": "Kilometer",
diff --git a/i18n/en.json b/i18n/en.json
index f158b84d7..3de6e1ef1 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -931,6 +931,11 @@
"label": "Design",
"light": "light"
},
+ "time": {
+ "12h": "12h",
+ "24h": "24h",
+ "label": "Time format"
+ },
"title": "User Interface",
"unit": {
"km": "km",