Fix browser time format handling (#22694)

* implement hook to return resolved "24hour" | "12hour" string

delegate to existing use24HourTime(), which correctly detects the browser's locale preference via Intl.DateTimeFormat

* update frontend to use use24HourTime(config) or useTimeFormat(config) instead of directly comparing config.ui.time_format
This commit is contained in:
Josh Hawkins
2026-03-29 13:03:07 -06:00
committed by GitHub
parent f44f485f48
commit 257dae11c1
23 changed files with 165 additions and 133 deletions
+3 -2
View File
@@ -19,7 +19,7 @@ import { useResizeObserver } from "@/hooks/resize-observer";
import { Skeleton } from "@/components/ui/skeleton";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import TimeAgo from "@/components/dynamic/TimeAgo";
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
import { useFormattedTimestamp, use24HourTime } from "@/hooks/use-date-utils";
import { FrigateConfig } from "@/types/frigateConfig";
const MOTION_HEATMAP_GRID_SIZE = 16;
@@ -165,9 +165,10 @@ function MotionPreviewClip({
const [fallbackFrameIndex, setFallbackFrameIndex] = useState(0);
const [fallbackFramesReady, setFallbackFramesReady] = useState(false);
const is24Hour = use24HourTime(config);
const formattedDate = useFormattedTimestamp(
range.start_time,
config?.ui.time_format == "24hour"
is24Hour
? t("time.formattedTimestampMonthDayHourMinute.24hour", {
ns: "common",
})
@@ -43,8 +43,9 @@ import { TimezoneAwareCalendar } from "@/components/overlay/ReviewActivityCalend
import { useApiHost } from "@/api";
import { useResizeObserver } from "@/hooks/resize-observer";
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
import { useFormattedTimestamp, use24HourTime } from "@/hooks/use-date-utils";
import { getUTCOffset } from "@/utils/dateUtil";
import useSWR from "swr";
import { cn } from "@/lib/utils";
import MotionSearchROICanvas from "./MotionSearchROICanvas";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
@@ -452,7 +453,6 @@ export default function MotionSearchDialog({
range={searchRange}
setRange={setSearchRange}
defaultRange={defaultRange}
timeFormat={config.ui?.time_format}
timezone={timezone}
/>
@@ -476,7 +476,6 @@ type SearchRangeSelectorProps = {
range?: TimeRange;
setRange: React.Dispatch<React.SetStateAction<TimeRange | undefined>>;
defaultRange: TimeRange;
timeFormat?: "browser" | "12hour" | "24hour";
timezone?: string;
};
@@ -484,7 +483,6 @@ function SearchRangeSelector({
range,
setRange,
defaultRange,
timeFormat,
timezone,
}: SearchRangeSelectorProps) {
const { t } = useTranslation(["views/motionSearch", "common"]);
@@ -527,15 +525,18 @@ function SearchRangeSelector({
return time;
}, [range, defaultRange, timezoneOffset, localTimeOffset]);
const { data: config } = useSWR<FrigateConfig>("config");
const is24Hour = use24HourTime(config);
const formattedStart = useFormattedTimestamp(
startTime,
timeFormat === "24hour"
is24Hour
? t("time.formattedTimestamp.24hour", { ns: "common" })
: t("time.formattedTimestamp.12hour", { ns: "common" }),
);
const formattedEnd = useFormattedTimestamp(
endTime,
timeFormat === "24hour"
is24Hour
? t("time.formattedTimestamp.24hour", { ns: "common" })
: t("time.formattedTimestamp.12hour", { ns: "common" }),
);
@@ -51,7 +51,7 @@ import {
RecordingSegment,
} from "@/types/record";
import { VideoResolutionType } from "@/types/live";
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
import { useFormattedTimestamp, use24HourTime } from "@/hooks/use-date-utils";
import MotionSearchROICanvas from "./MotionSearchROICanvas";
import MotionSearchDialog from "./MotionSearchDialog";
import { IoMdArrowRoundBack } from "react-icons/io";
@@ -94,12 +94,13 @@ export default function MotionSearchView({
]);
const navigate = useNavigate();
const is24Hour = use24HourTime(config);
const resultTimestampFormat = useMemo(
() =>
config.ui?.time_format === "24hour"
is24Hour
? t("time.formattedTimestamp.24hour", { ns: "common" })
: t("time.formattedTimestamp.12hour", { ns: "common" }),
[config.ui?.time_format, t],
[is24Hour, t],
);
// Refs
+4 -4
View File
@@ -31,6 +31,7 @@ import Chip from "@/components/indicators/Chip";
import { TooltipPortal } from "@radix-ui/react-tooltip";
import SearchActionGroup from "@/components/filter/SearchActionGroup";
import { Trans, useTranslation } from "react-i18next";
import { use24HourTime } from "@/hooks/use-date-utils";
import { useNavigate } from "react-router-dom";
import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
@@ -77,6 +78,7 @@ export default function SearchView({
const { data: config } = useSWR<FrigateConfig>("config", {
revalidateOnFocus: false,
});
const is24Hour = use24HourTime(config);
const navigate = useNavigate();
const { data: exploreEvents } = useSWR<SearchResult[]>(
@@ -191,10 +193,7 @@ export default function SearchView({
sub_labels: allSubLabels,
...(hasCustomClassificationModels && { attributes: allAttributes }),
search_type: ["thumbnail", "description"] as SearchSource[],
time_range:
config?.ui.time_format == "24hour"
? ["00:00-23:59"]
: ["12:00AM-11:59PM"],
time_range: is24Hour ? ["00:00-23:59"] : ["12:00AM-11:59PM"],
before: [formatDateToLocaleString()],
after: [formatDateToLocaleString(-5)],
min_score: ["50"],
@@ -209,6 +208,7 @@ export default function SearchView({
}),
[
config,
is24Hour,
allLabels,
allZones,
allSubLabels,
+28 -28
View File
@@ -39,6 +39,7 @@ import { Trigger, TriggerAction, TriggerType } from "@/types/trigger";
import { useSearchEffect } from "@/hooks/use-overlay-state";
import { cn } from "@/lib/utils";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import { use24HourTime } from "@/hooks/use-date-utils";
import { Link } from "react-router-dom";
import { useTriggers } from "@/api/ws";
import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
@@ -89,6 +90,7 @@ export default function TriggerView({
const { t } = useTranslation("views/settings");
const { data: config, mutate: updateConfig } =
useSWR<FrigateConfig>("config");
const is24Hour = use24HourTime(config);
const { data: trigger_status, mutate } = useSWR(
config?.cameras[selectedCamera]?.semantic_search?.triggers &&
Object.keys(config.cameras[selectedCamera].semantic_search.triggers)
@@ -581,20 +583,19 @@ export default function TriggerView({
?.last_triggered,
{
timezone: config.ui.timezone,
date_format:
config.ui.time_format == "24hour"
? t(
"time.formattedTimestamp2.24hour",
{
ns: "common",
},
)
: t(
"time.formattedTimestamp2.12hour",
{
ns: "common",
},
),
date_format: is24Hour
? t(
"time.formattedTimestamp2.24hour",
{
ns: "common",
},
)
: t(
"time.formattedTimestamp2.12hour",
{
ns: "common",
},
),
time_style: "medium",
date_style: "medium",
},
@@ -742,20 +743,19 @@ export default function TriggerView({
?.last_triggered,
{
timezone: config.ui.timezone,
date_format:
config.ui.time_format == "24hour"
? t(
"time.formattedTimestamp2.24hour",
{
ns: "common",
},
)
: t(
"time.formattedTimestamp2.12hour",
{
ns: "common",
},
),
date_format: is24Hour
? t(
"time.formattedTimestamp2.24hour",
{
ns: "common",
},
)
: t(
"time.formattedTimestamp2.12hour",
{
ns: "common",
},
),
time_style: "medium",
date_style: "medium",
},
+6 -2
View File
@@ -10,7 +10,11 @@ import {
import useSWR from "swr";
import { CiCircleAlert } from "react-icons/ci";
import { FrigateConfig } from "@/types/frigateConfig";
import { useFormattedTimestamp, useTimezone } from "@/hooks/use-date-utils";
import {
useFormattedTimestamp,
useTimeFormat,
useTimezone,
} from "@/hooks/use-date-utils";
import { RecordingsSummary } from "@/types/review";
import { useTranslation } from "react-i18next";
import { TZDate } from "react-day-picker";
@@ -81,7 +85,7 @@ export default function StorageMetrics({
: null;
}, [recordingsSummary, timezone]);
const timeFormat = config?.ui.time_format === "24hour" ? "24hour" : "12hour";
const timeFormat = useTimeFormat(config);
const format = useMemo(() => {
return t(`time.formattedTimestampMonthDayYear.${timeFormat}`, {
ns: "common",