Skip to content

Commit dadda7b

Browse files
fix: Data usage graphs don't work in UTC-n time zones (#9530)
Fixes a number of issues that would surface in UTC-n (where n > 1) timezones. I've not found a way to check this with tests (and it looks like [we weren't able to last time either](https://github.com/Unleash/unleash/pull/9110/files#r1919746328)), so all the testing's been done manually by adjusting my system time and zone. (From what I understand, you can't generate a Date with a specific TZ offset in JS: it's only utc or local time) Resolved: - [x] Selecting "Jan" in the dropdown results in the selection being "December" (off by one in the selector) - [x] Selecting a month view only gives you one data point (and it's probably empty). Wrong date parsing on the way out resulted in sending `{ from: "2025-02-28", to: "2025-02-28"}` instead of `{ from: "2025-03-01", to: "2025-03-31"}` - [x] The dates we create when making "daysRec" need to be adjusted. They showed the wrong month, so the dates were off. - [x] Make sure the labels are correct when hovering over. Again: we used the wrong month for generating these. - [x] The available months are wrong. Incorrect month parsing again. - [x] The request summary month is wrong. You guessed it: incorrect month parsing
1 parent 0aae3ba commit dadda7b

9 files changed

+42
-27
lines changed

frontend/src/component/admin/network/NetworkTrafficUsage/PeriodSelector.tsx

+9-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useRef, useState, type FC } from 'react';
55
import { format } from 'date-fns';
66
import type { ChartDataSelection } from './chart-data-selection';
77
import { selectablePeriods } from './selectable-periods';
8+
import { parseMonthString } from './dates';
89

910
const dropdownWidth = '15rem';
1011
const dropdownInlinePadding = (theme: Theme) => theme.spacing(3);
@@ -148,10 +149,13 @@ export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
148149
selectedPeriod.grouping === 'daily'
149150
? selectedPeriod.month === format(new Date(), 'yyyy-MM')
150151
? 'Current month'
151-
: new Date(selectedPeriod.month).toLocaleDateString('en-US', {
152-
month: 'long',
153-
year: 'numeric',
154-
})
152+
: parseMonthString(selectedPeriod.month).toLocaleDateString(
153+
'en-US',
154+
{
155+
month: 'long',
156+
year: 'numeric',
157+
},
158+
)
155159
: `Last ${selectedPeriod.monthsBack} months`;
156160

157161
return (
@@ -184,7 +188,7 @@ export const PeriodSelector: FC<Props> = ({ selectedPeriod, setPeriod }) => {
184188
<p>Last 12 months</p>
185189
</MonthSelectorHeaderGroup>
186190
<MonthGrid>
187-
{selectablePeriods.map((period, index) => (
191+
{selectablePeriods.map((period) => (
188192
<li key={period.label}>
189193
<GridButton
190194
selected={

frontend/src/component/admin/network/NetworkTrafficUsage/RequestSummary.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { subMonths } from 'date-fns';
44
import { useLocationSettings } from 'hooks/useLocationSettings';
55
import type { FC } from 'react';
66
import type { ChartDataSelection } from './chart-data-selection';
7+
import { parseMonthString } from './dates';
78

89
type Props = {
910
period: ChartDataSelection;
@@ -62,7 +63,7 @@ const incomingRequestsText = (period: ChartDataSelection): string => {
6263
return `Average requests from ${formatMonth(fromMonth)} to ${formatMonth(toMonth)}`;
6364
}
6465

65-
return `Incoming requests in ${formatMonth(new Date(period.month))}`;
66+
return `Incoming requests in ${formatMonth(parseMonthString(period.month))}`;
6667
};
6768

6869
export const RequestSummary: FC<Props> = ({

frontend/src/component/admin/network/NetworkTrafficUsage/chart-data-selection.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { endOfMonth, format, startOfMonth, subMonths } from 'date-fns';
2+
import { parseMonthString } from './dates';
23

34
export type ChartDataSelection =
45
| {
@@ -16,7 +17,7 @@ export const toDateRange = (
1617
): { from: string; to: string } => {
1718
const fmt = (date: Date) => format(date, 'yyyy-MM-dd');
1819
if (selection.grouping === 'daily') {
19-
const month = new Date(selection.month);
20+
const month = parseMonthString(selection.month);
2021
const from = fmt(month);
2122
const to = fmt(endOfMonth(month));
2223
return { from, to };

frontend/src/component/admin/network/NetworkTrafficUsage/chart-functions.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
differenceInCalendarDays,
1111
differenceInCalendarMonths,
1212
} from 'date-fns';
13-
import { formatDay, formatMonth } from './dates';
13+
import { formatDay, formatMonth, parseDateString } from './dates';
1414
import type { ChartDataSelection } from './chart-data-selection';
1515
export type ChartDatasetType = ChartDataset<'bar'>;
1616

@@ -80,8 +80,8 @@ const getLabelsAndRecords = (
8080
>,
8181
) => {
8282
if (traffic.grouping === 'monthly') {
83-
const from = new Date(traffic.dateRange.from);
84-
const to = new Date(traffic.dateRange.to);
83+
const from = parseDateString(traffic.dateRange.from);
84+
const to = parseDateString(traffic.dateRange.to);
8585
const numMonths = Math.abs(differenceInCalendarMonths(to, from)) + 1;
8686
const monthsRec: { [month: string]: number } = {};
8787
for (let i = 0; i < numMonths; i++) {
@@ -95,8 +95,8 @@ const getLabelsAndRecords = (
9595
);
9696
return { newRecord: () => ({ ...monthsRec }), labels };
9797
} else {
98-
const from = new Date(traffic.dateRange.from);
99-
const to = new Date(traffic.dateRange.to);
98+
const from = parseDateString(traffic.dateRange.from);
99+
const to = parseDateString(traffic.dateRange.to);
100100
const numDays = Math.abs(differenceInCalendarDays(to, from)) + 1;
101101
const daysRec: { [day: string]: number } = {};
102102
for (let i = 0; i < numDays; i++) {

frontend/src/component/admin/network/NetworkTrafficUsage/dates.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { format, getDaysInMonth } from 'date-fns';
1+
import { format, getDaysInMonth, parse } from 'date-fns';
22

33
export const currentDate = new Date();
44

@@ -11,3 +11,13 @@ export const daysInCurrentMonth = getDaysInMonth(currentDate);
1111

1212
export const formatMonth = (date: Date) => format(date, 'yyyy-MM');
1313
export const formatDay = (date: Date) => format(date, 'yyyy-MM-dd');
14+
15+
export const parseMonthString = (month: string): Date => {
16+
// parses a month into a Date starting on the first day of the month, regardless of the current time zone (e.g. works in Norway and Brazil)
17+
return parse(month, 'yyyy-MM', new Date());
18+
};
19+
20+
export const parseDateString = (month: string): Date => {
21+
// parses a date string into a Date, regardless of the current time zone (e.g. works in Norway and Brazil)
22+
return parse(month, 'yyyy-MM-dd', new Date());
23+
};

frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useChartDataSelection.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { periodsRecord, selectablePeriods } from '../selectable-periods';
44
import { createBarChartOptions } from '../bar-chart-options';
55
import useTheme from '@mui/material/styles/useTheme';
66
import { useLocationSettings } from 'hooks/useLocationSettings';
7+
import { parseMonthString } from '../dates';
78

89
export const useChartDataSelection = (includedTraffic?: number) => {
910
const theme = useTheme();
@@ -35,11 +36,11 @@ export const useChartDataSelection = (includedTraffic?: number) => {
3536
},
3637
);
3738
} else {
38-
const timestamp = Date.parse(tooltipItems[0].label);
39-
if (Number.isNaN(timestamp)) {
39+
const month = parseMonthString(tooltipItems[0].label);
40+
if (Number.isNaN(month.getTime())) {
4041
return 'Current month to date';
4142
}
42-
return new Date(timestamp).toLocaleDateString(
43+
return month.toLocaleDateString(
4344
locationSettings?.locale ?? 'en-US',
4445
{
4546
month: 'long',

frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useStats.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { useTrafficSearch } from 'hooks/api/getters/useInstanceTrafficMetrics/us
33
import { currentDate } from '../dates';
44
import { useMemo } from 'react';
55
import {
6-
toTrafficUsageChartData as newToChartData,
76
toConnectionChartData,
7+
toTrafficUsageChartData,
88
} from '../chart-functions';
99
import {
1010
calculateEstimatedMonthlyCost,
@@ -36,7 +36,7 @@ export const useTrafficStats = (
3636
}
3737
const traffic = result.data;
3838

39-
const chartData = newToChartData(traffic, filter);
39+
const chartData = toTrafficUsageChartData(traffic, filter);
4040
const usageTotal = calculateTotalUsage(traffic);
4141
const overageCost = calculateOverageCost(
4242
usageTotal,

frontend/src/component/admin/network/NetworkTrafficUsage/selectable-periods.ts

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getDaysInMonth } from 'date-fns';
1+
import { getDaysInMonth, startOfMonth, subMonths } from 'date-fns';
22
import { currentDate, formatMonth } from './dates';
33
import { TRAFFIC_MEASUREMENT_START_DATE } from 'utils/traffic-calculations';
44

@@ -38,21 +38,18 @@ export const toSelectablePeriod = (
3838

3939
export const generateSelectablePeriodsFromDate = (now: Date) => {
4040
const selectablePeriods = [toSelectablePeriod(now, 'Current month')];
41+
const startOfCurrentMonth = startOfMonth(now);
4142
for (
4243
let subtractMonthCount = 1;
4344
subtractMonthCount < 12;
4445
subtractMonthCount++
4546
) {
46-
// this complicated calc avoids DST issues
47-
const utcYear = now.getUTCFullYear();
48-
const utcMonth = now.getUTCMonth();
49-
const targetMonth = utcMonth - subtractMonthCount;
50-
const targetDate = new Date(Date.UTC(utcYear, targetMonth, 1, 0, 0, 0));
47+
const targetMonth = subMonths(startOfCurrentMonth, subtractMonthCount);
5148
selectablePeriods.push(
5249
toSelectablePeriod(
53-
targetDate,
50+
targetMonth,
5451
undefined,
55-
targetDate >= TRAFFIC_MEASUREMENT_START_DATE,
52+
targetMonth >= TRAFFIC_MEASUREMENT_START_DATE,
5653
),
5754
);
5855
}

frontend/src/utils/traffic-calculations.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import type {
44
} from 'openapi';
55
import { getDaysInMonth } from 'date-fns';
66
import { format } from 'date-fns';
7+
import { parseDateString } from 'component/admin/network/NetworkTrafficUsage/dates';
78
export const DEFAULT_TRAFFIC_DATA_UNIT_COST = 5;
89
export const DEFAULT_TRAFFIC_DATA_UNIT_SIZE = 1_000_000;
910

10-
export const TRAFFIC_MEASUREMENT_START_DATE = new Date('2024-05-01');
11+
export const TRAFFIC_MEASUREMENT_START_DATE = parseDateString('2024-05-01');
1112

1213
export const METERED_TRAFFIC_ENDPOINTS = [
1314
'/api/admin',

0 commit comments

Comments
 (0)