diff --git a/web/src/components/logstream/schema.vue b/web/src/components/logstream/schema.vue index 2e6bf706b3..830a598025 100644 --- a/web/src/components/logstream/schema.vue +++ b/web/src/components/logstream/schema.vue @@ -41,7 +41,7 @@ along with this program. If not, see . - {{ t("logStream.utc") }} + {{ displayTimezone }}
{{ 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, diff --git a/web/src/utils/date.ts b/web/src/utils/date.ts index 99c7d5d423..42c18b3461 100644 --- a/web/src/utils/date.ts +++ b/web/src/utils/date.ts @@ -14,6 +14,7 @@ // along with this program. If not, see . 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.