fix(logstream): show local timezone in stream detail time range (#13652)

## Problem

The **Stream Detail** header shows the stream's document time range with
a timezone badge that is **hardcoded to \`UTC\`** (i18n key
\`logStream.utc\`), while the timestamps next to it are rendered by
date-fns \`format()\` in the **browser's local timezone**. So the badge
and the value disagree for any non-UTC user — the range reads as local
time but is mislabeled "UTC".

## Fix

- Add \`formatTimestampInTimezone(us, format, timezone)\` in
\`web/src/utils/date.ts\` — same as \`formatTimestamp\` but renders in a
given IANA zone (via \`date-fns-tz\` \`formatInTimeZone\`, already a
dependency), matching the pattern already used across dashboards.
- In \`schema.vue\`, add a \`displayTimezone\` computed =
\`store.state.timezone || <browser zone>\` (never a hardcoded "UTC"),
render it in the badge, and format the \`doc_time_min\`/\`doc_time_max\`
range in that same timezone.

Now the badge label and the displayed range always use the same timezone
— the user's selected timezone, falling back to their browser zone.

## Scope note

The separate **Extended Retention** date picker further down still uses
UTC intentionally (retention day-boundaries are computed in UTC on the
backend), so its \`(UTC Timezone)\` label is left unchanged.

## Validation

Verified \`formatInTimeZone\` behavior against the installed
\`date-fns-tz@3.2.0\`:

| timezone | rendered value |
|---|---|
| UTC | \`2025-08-03T20:03:22:99\` |
| Asia/Kolkata | \`2025-08-04T01:33:22:99\` |
| America/New_York | \`2025-08-03T16:03:22:99\` |

Label and value now agree in every case.
This commit is contained in:
Shrinath Rao
2026-08-06 04:26:06 +00:00
committed by GitHub
parent 13acdd61da
commit c9f68754df
2 changed files with 25 additions and 4 deletions
+15 -4
View File
@@ -41,7 +41,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<span
class="text-3xs rounded-default text-text-secondary bg-surface-subtle px-1.5 py-0.5 font-medium"
>
{{ t("logStream.utc") }}
{{ displayTimezone }}
</span>
<div class="text-text-body text-xs font-semibold">
{{ indexData.stats.doc_time_min }}
@@ -792,7 +792,10 @@ import { computed, defineComponent, onBeforeMount, ref, watch } from "vue";
import { raw, useI18nTyped, type I18nText } from "@/types/i18n";
import { useStore } from "vuex";
import useTheme from "@/composables/useTheme";
import { convertUnixToDateFormat as convertUnixToFormat, formatTimestamp } from "@/utils/date";
import {
convertUnixToDateFormat as convertUnixToFormat,
formatTimestampInTimezone,
} from "@/utils/date";
import streamService from "../../services/stream";
import segment from "../../services/segment_analytics";
import {
@@ -917,6 +920,11 @@ export default defineComponent({
const { t } = useI18nTyped();
const store = useStore();
const { isDark } = useTheme();
// Timezone used for the stream-stats time range: the user's selected
// timezone, falling back to the browser's zone (never a hardcoded "UTC").
const displayTimezone = computed(
() => store.state.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
);
const indexData: any = ref(defaultValue());
const updateSettingsForm: any = ref(null);
const isCloud = config.isCloud;
@@ -1355,13 +1363,15 @@ export default defineComponent({
indexData.value.stats.original_doc_time_max = streamResponse.stats.doc_time_max;
indexData.value.stats.original_doc_time_min = streamResponse.stats.doc_time_min;
indexData.value.stats.doc_time_max = formatTimestamp(
indexData.value.stats.doc_time_max = formatTimestampInTimezone(
parseInt(streamResponse.stats.doc_time_max),
"YYYY-MM-DDTHH:mm:ss:SS",
displayTimezone.value,
);
indexData.value.stats.doc_time_min = formatTimestamp(
indexData.value.stats.doc_time_min = formatTimestampInTimezone(
parseInt(streamResponse.stats.doc_time_min),
"YYYY-MM-DDTHH:mm:ss:SS",
displayTimezone.value,
);
indexData.value.defined_schema_fields = streamResponse.settings.defined_schema_fields || [];
@@ -2439,6 +2449,7 @@ export default defineComponent({
t,
raw,
store,
displayTimezone,
config,
dateChangeValue,
isCloud,
+10
View File
@@ -14,6 +14,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { format as dfFormat, sub } from "date-fns";
import { formatInTimeZone } from "date-fns-tz";
import { DateTime as _DateTime } from "luxon";
// ---------------------------------------------------------------------------
@@ -267,6 +268,15 @@ export function formatTimestamp(us: number, format: string): string {
return formatDate(us / 1000, format);
}
/**
* Format a microsecond timestamp in a specific IANA timezone.
* Mirrors formatTimestamp but renders in the given timezone instead of the
* browser's local zone, so the displayed value matches a timezone label.
*/
export function formatTimestampInTimezone(us: number, format: string, timezone: string): string {
return formatInTimeZone(us / 1000, timezone, normalizeFormat(format));
}
/**
* Format a nanosecond timestamp to a string.
* Converts nanoseconds to milliseconds internally.