diff --git a/apps/studio/components/interfaces/Reports/ReportBlock/ChartBlock.tsx b/apps/studio/components/interfaces/Reports/ReportBlock/ChartBlock.tsx index 0ba4453a9a6..81fb051976e 100644 --- a/apps/studio/components/interfaces/Reports/ReportBlock/ChartBlock.tsx +++ b/apps/studio/components/interfaces/Reports/ReportBlock/ChartBlock.tsx @@ -27,6 +27,7 @@ import { useProjectDailyStatsQuery, } from '@/data/analytics/project-daily-stats-query' import { METRICS } from '@/lib/constants/metrics' +import { useFormatDateTime } from '@/lib/datetime' import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector' import type { Dashboards } from '@/types' @@ -72,6 +73,7 @@ export const ChartBlock = ({ const [chartStyle, setChartStyle] = useState(defaultChartStyle) const logScale = useMemo(() => defaultLogScale, [defaultLogScale]) const [latestValue, setLatestValue] = useState() + const formatChartDate = useFormatDateTime() const databaseIdentifier = state.selectedDatabaseId @@ -316,7 +318,7 @@ export const ChartBlock = ({ dayjs(x).format('DD MMM YYYY')} + labelFormatter={(x) => formatChartDate(x as string | number, 'DD MMM YYYY')} /> } /> @@ -343,7 +345,7 @@ export const ChartBlock = ({ content={ dayjs(x).format('DD MMM YYYY')} + labelFormatter={(x) => formatChartDate(x as string | number, 'DD MMM YYYY')} /> } /> diff --git a/apps/studio/components/ui/Charts/AreaChart.tsx b/apps/studio/components/ui/Charts/AreaChart.tsx index f8e5c343b9b..e04c26c6a5e 100644 --- a/apps/studio/components/ui/Charts/AreaChart.tsx +++ b/apps/studio/components/ui/Charts/AreaChart.tsx @@ -1,4 +1,3 @@ -import dayjs from 'dayjs' import { useState } from 'react' import { Area, AreaChart as RechartAreaChart, Tooltip, XAxis } from 'recharts' @@ -8,6 +7,7 @@ import { numberFormatter, useChartSize } from './Charts.utils' import NoDataPlaceholder from './NoDataPlaceholder' import { useChartHoverState } from './useChartHoverState' import { CHART_COLORS, DateTimeFormats } from '@/components/ui/Charts/Charts.constants' +import { formatDateTime, useFormatDateTime } from '@/lib/datetime' export interface AreaChartProps extends CommonChartProps { yAxisKey: string @@ -40,12 +40,19 @@ const AreaChart = ({ ) const [focusDataIndex, setFocusDataIndex] = useState(null) - const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value)) + // When `displayDateInUtc` is set the chart explicitly wants UTC labels (used + // by views that display server time). Otherwise honour the user's selected + // timezone via the picker, which `useFormatDateTime` reads from context. + const formatPickerDate = useFormatDateTime() + const formatChartDate = (value: number | string) => + displayDateInUtc + ? formatDateTime(value, { tz: 'UTC', format: customDateFormat }) + : formatPickerDate(value, customDateFormat) const resolvedHighlightedLabel = (focusDataIndex !== null && data && data[focusDataIndex] !== undefined && - day(data[focusDataIndex][xAxisKey]).format(customDateFormat)) || + formatChartDate(data[focusDataIndex][xAxisKey])) || highlightedLabel const resolvedHighlightedValue = @@ -129,7 +136,7 @@ const AreaChart = ({ syncId && syncTooltip && hoveredIndex !== null ? (
- {dayjs(data[hoveredIndex]?.[xAxisKey]).format(customDateFormat)} + {formatChartDate(data[hoveredIndex]?.[xAxisKey] as number | string)}
{numberFormatter(Number(data[hoveredIndex]?.[yAxisKey]) || 0, valuePrecision)} @@ -150,8 +157,8 @@ const AreaChart = ({ {data && (
- {dayjs(data[0][xAxisKey]).format(customDateFormat)} - {dayjs(data[data?.length - 1]?.[xAxisKey]).format(customDateFormat)} + {formatChartDate(data[0][xAxisKey] as number | string)} + {formatChartDate(data[data?.length - 1]?.[xAxisKey] as number | string)}
)}
diff --git a/apps/studio/components/ui/Charts/BarChart.tsx b/apps/studio/components/ui/Charts/BarChart.tsx index 4b49f61fc9d..ee94de47981 100644 --- a/apps/studio/components/ui/Charts/BarChart.tsx +++ b/apps/studio/components/ui/Charts/BarChart.tsx @@ -1,4 +1,3 @@ -import dayjs from 'dayjs' import { ComponentProps, useMemo, useState } from 'react' import { Bar, @@ -18,6 +17,7 @@ import { numberFormatter, useChartSize } from './Charts.utils' import NoDataPlaceholder from './NoDataPlaceholder' import { useChartHoverState } from './useChartHoverState' import { CHART_COLORS, DateTimeFormats } from '@/components/ui/Charts/Charts.constants' +import { formatDateTime, useFormatDateTime } from '@/lib/datetime' export interface BarChartProps extends CommonChartProps { yAxisKey: string @@ -83,7 +83,14 @@ function BarChart({ width: 0, } - const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value)) + // When `displayDateInUtc` is set the chart explicitly wants UTC labels. + // Otherwise honour the user's selected timezone via the picker, which + // `useFormatDateTime` reads from context. + const formatPickerDate = useFormatDateTime() + const formatChartDate = (value: number | string) => + displayDateInUtc + ? formatDateTime(value, { tz: 'UTC', format: customDateFormat }) + : formatPickerDate(value, customDateFormat) function getHeaderLabel() { if (!xAxisIsDate) { @@ -94,7 +101,7 @@ function BarChart({ (focusDataIndex !== null && data && data[focusDataIndex] !== undefined && - day(data[focusDataIndex][xAxisKey]).format(customDateFormat)) || + formatChartDate(data[focusDataIndex][xAxisKey] as number | string)) || highlightedLabel ) } @@ -175,7 +182,7 @@ function BarChart({ syncId && isHovered && isCurrentChart && hoveredIndex !== null ? (
- {dayjs(data[hoveredIndex]?.[xAxisKey]).format(customDateFormat)} + {formatChartDate(data[hoveredIndex]?.[xAxisKey] as number | string)}
{numberFormatter(Number(data[hoveredIndex]?.[yAxisKey]) || 0, valuePrecision)} @@ -209,11 +216,13 @@ function BarChart({ {data && (
- {xAxisIsDate ? day(data[0][xAxisKey]).format(customDateFormat) : data[0][xAxisKey]} + {xAxisIsDate + ? formatChartDate(data[0][xAxisKey] as number | string) + : data[0][xAxisKey]} {xAxisIsDate - ? day(data[data?.length - 1]?.[xAxisKey]).format(customDateFormat) + ? formatChartDate(data[data?.length - 1]?.[xAxisKey] as number | string) : data[data?.length - 1]?.[xAxisKey]}
diff --git a/apps/studio/components/ui/Charts/ChartHeader.tsx b/apps/studio/components/ui/Charts/ChartHeader.tsx index d7c2e8d0203..6a77a2a8375 100644 --- a/apps/studio/components/ui/Charts/ChartHeader.tsx +++ b/apps/studio/components/ui/Charts/ChartHeader.tsx @@ -1,5 +1,4 @@ import { useParams } from 'common' -import dayjs from 'dayjs' import { Activity, BarChartIcon, @@ -15,6 +14,7 @@ import { InfoTooltip } from 'ui-patterns/info-tooltip' import { formatPercentage, numberFormatter } from './Charts.utils' import { useChartHoverState } from './useChartHoverState' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' +import { formatDateTime, useFormatDateTime } from '@/lib/datetime' import { formatBytes } from '@/lib/helpers' export interface ChartHeaderProps { @@ -83,6 +83,10 @@ export const ChartHeader = ({ const [localHighlightedValue, setLocalHighlightedValue] = useState(highlightedValue) const [localHighlightedLabel, setLocalHighlightedLabel] = useState(highlightedLabel) + // When `displayDateInUtc` is set the chart explicitly wants UTC labels. + // Otherwise honour the user's selected timezone via the picker. + const formatPickerDate = useFormatDateTime() + const formatHighlightedValue = (value: any) => { if (typeof value !== 'number') { return value @@ -146,11 +150,11 @@ export const ChartHeader = ({ // Update highlighted label based on sync state let newLabel = highlightedLabel if (xAxisIsDate && activeDataPoint[xAxisKey]) { - const day = (value: number | string) => - displayDateInUtc ? dayjs(value).utc() : dayjs(value) - newLabel = day(activeDataPoint[xAxisKey]).format( - customDateFormat || 'YYYY-MM-DD HH:mm:ss' - ) + const value = activeDataPoint[xAxisKey] as number | string + const fmt = customDateFormat || 'YYYY-MM-DD HH:mm:ss' + newLabel = displayDateInUtc + ? formatDateTime(value, { tz: 'UTC', format: fmt }) + : formatPickerDate(value, fmt) } else if (activeDataPoint[xAxisKey]) { newLabel = activeDataPoint[xAxisKey] } @@ -174,6 +178,7 @@ export const ChartHeader = ({ highlightedValue, highlightedLabel, attributes, + formatPickerDate, ]) const chartTitle = ( diff --git a/apps/studio/components/ui/Charts/ChartHighlightActions.tsx b/apps/studio/components/ui/Charts/ChartHighlightActions.tsx index f4841fb7ccb..2babb10d0f6 100644 --- a/apps/studio/components/ui/Charts/ChartHighlightActions.tsx +++ b/apps/studio/components/ui/Charts/ChartHighlightActions.tsx @@ -12,6 +12,7 @@ import { } from 'ui' import { ChartHighlight } from './useChartHighlight' +import { useFormatDateTime } from '@/lib/datetime' export type UpdateDateRange = (from: string, to: string) => void @@ -44,6 +45,7 @@ export const ChartHighlightActions = ({ }) => { const { left: selectedRangeStart, right: selectedRangeEnd, clearHighlight } = chartHighlight ?? {} const [isOpen, setIsOpen] = useState(!!chartHighlight?.popoverPosition) + const formatChartDate = useFormatDateTime() useEffect(() => { setIsOpen(!!chartHighlight?.popoverPosition && selectedRangeStart !== selectedRangeEnd) @@ -90,9 +92,9 @@ export const ChartHighlightActions = ({ /> - {dayjs(selectedRangeStart).format('MMM D, H:mm')} + {formatChartDate(selectedRangeStart!, 'MMM D, H:mm')} - {dayjs(selectedRangeEnd).format('MMM D, H:mm')} + {formatChartDate(selectedRangeEnd!, 'MMM D, H:mm')} {allActions.map((action) => { diff --git a/apps/studio/components/ui/Charts/ComposedChart.tsx b/apps/studio/components/ui/Charts/ComposedChart.tsx index f07c6a62b9e..188e2c479cc 100644 --- a/apps/studio/components/ui/Charts/ComposedChart.tsx +++ b/apps/studio/components/ui/Charts/ComposedChart.tsx @@ -1,4 +1,3 @@ -import dayjs from 'dayjs' import { useTheme } from 'next-themes' import { ComponentProps, useEffect, useMemo, useState } from 'react' import { @@ -44,6 +43,7 @@ import { import NoDataPlaceholder from './NoDataPlaceholder' import { ChartHighlight } from './useChartHighlight' import { useChartHoverState } from './useChartHoverState' +import { formatDateTime, useFormatDateTime } from '@/lib/datetime' import { formatBytes } from '@/lib/helpers' export interface ComposedChartProps extends CommonChartProps { @@ -155,7 +155,13 @@ export function ComposedChart({ const { Container } = useChartSize(size) - const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value)) + // When `displayDateInUtc` is set the chart explicitly wants UTC labels. + // Otherwise honour the user's selected timezone via the picker. + const formatPickerDate = useFormatDateTime() + const formatChartDate = (value: number | string) => + displayDateInUtc + ? formatDateTime(value, { tz: 'UTC', format: customDateFormat }) + : formatPickerDate(value, customDateFormat) const formatTimestamp = (ts: unknown) => { if (typeof ts !== 'number' && typeof ts !== 'string') { @@ -163,10 +169,11 @@ export function ComposedChart({ } if (typeof ts === 'number' && ts > 1e14) { - return day(ts / 1000).format(customDateFormat) + // Microsecond timestamp; convert to milliseconds before formatting. + return formatChartDate(ts / 1000) } - return day(ts).format(customDateFormat) + return formatChartDate(ts) } const _XAxisProps = XAxisProps || { diff --git a/apps/studio/components/ui/Charts/ComposedChart.utils.tsx b/apps/studio/components/ui/Charts/ComposedChart.utils.tsx index f7f635d722b..288fe4033f3 100644 --- a/apps/studio/components/ui/Charts/ComposedChart.utils.tsx +++ b/apps/studio/components/ui/Charts/ComposedChart.utils.tsx @@ -1,12 +1,11 @@ 'use client' -import dayjs from 'dayjs' import { useState } from 'react' import { cn, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'ui' import { CHART_COLORS, DateTimeFormats } from './Charts.constants' import { formatPercentage, numberFormatter } from './Charts.utils' -import { guessLocalTimezone } from '@/lib/dayjs' +import { useFormatDateTime, useTimezone } from '@/lib/datetime' import { formatBytes } from '@/lib/helpers' export interface ReportAttributes { @@ -154,6 +153,8 @@ export const CustomTooltip = ({ showTotal, isActiveHoveredChart, }: TooltipProps) => { + const formatDateTime = useFormatDateTime() + const { timezone } = useTimezone() if (active && payload && payload.length) { /** * Depending on the data source, the timestamp key could be 'timestamp' or 'period_start' @@ -194,7 +195,7 @@ export const CustomTooltip = ({ ...(maxValueAttribute?.attribute ? [maxValueAttribute.attribute] : []), ] - const localTimeZone = guessLocalTimezone() + const localTimeZone = timezone const rawPayload = payload.map((entry: any) => ({ ...entry, @@ -256,7 +257,7 @@ export const CustomTooltip = ({ )} >

{localTimeZone}

-

{dayjs(timestamp).format(DateTimeFormats.FULL_SECONDS)}

+

{formatDateTime(timestamp, DateTimeFormats.FULL_SECONDS)}

{[...payload].reverse().map((entry: any, index: number) => ( diff --git a/apps/studio/components/ui/Charts/StackedBarChart.tsx b/apps/studio/components/ui/Charts/StackedBarChart.tsx index bd6bc28ba56..444b3f67970 100644 --- a/apps/studio/components/ui/Charts/StackedBarChart.tsx +++ b/apps/studio/components/ui/Charts/StackedBarChart.tsx @@ -1,4 +1,3 @@ -import dayjs from 'dayjs' import { useState } from 'react' import { Bar, BarChart, Cell, Legend, Tooltip, XAxis } from 'recharts' @@ -11,15 +10,10 @@ import { ValidStackColor, } from './Charts.constants' import type { CommonChartProps } from './Charts.types' -import { - numberFormatter, - precisionFormatter, - timestampFormatter, - useChartSize, - useStacked, -} from './Charts.utils' +import { numberFormatter, precisionFormatter, useChartSize, useStacked } from './Charts.utils' import NoDataPlaceholder from './NoDataPlaceholder' import { useChartHoverState } from './useChartHoverState' +import { formatDateTime, useFormatDateTime } from '@/lib/datetime' interface Props extends CommonChartProps { xAxisKey: string @@ -69,12 +63,19 @@ const StackedBarChart: React.FC = ({ }) const [focusDataIndex, setFocusDataIndex] = useState(null) - const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value)) + // When `displayDateInUtc` is set the chart explicitly wants UTC labels. + // Otherwise honour the user's selected timezone via the picker. + const formatPickerDate = useFormatDateTime() + const formatChartDate = (value: number | string) => + displayDateInUtc + ? formatDateTime(value, { tz: 'UTC', format: customDateFormat }) + : formatPickerDate(value, customDateFormat) + const resolvedHighlightedLabel = (focusDataIndex !== null && data && data[focusDataIndex] !== undefined && - day(data[focusDataIndex][xAxisKey]).format(customDateFormat)) || + formatChartDate(data[focusDataIndex][xAxisKey])) || highlightedLabel const resolvedHighlightedValue = @@ -179,9 +180,7 @@ const StackedBarChart: React.FC = ({ ))} timestampFormatter(label, customDateFormat, displayDateInUtc) - : undefined + xAxisFormatAsDate ? (label) => formatChartDate(label as number | string) : undefined } formatter={(value, name, props) => { const suffix = format || '' @@ -209,19 +208,9 @@ const StackedBarChart: React.FC = ({ {stackedData && stackedData[0] && (
+ {formatChartDate(stackedData[0][xAxisKey] as number | string)} - {timestampFormatter( - stackedData[0][xAxisKey] as string, - customDateFormat, - displayDateInUtc - )} - - - {timestampFormatter( - stackedData[stackedData?.length - 1][xAxisKey] as string, - customDateFormat, - displayDateInUtc - )} + {formatChartDate(stackedData[stackedData?.length - 1][xAxisKey] as number | string)}
)}