import { addDays, addHours, differenceInCalendarDays, differenceInMinutes, format, isBefore, parseISO, toDate, } from 'date-fns'; import { e2e } from '@grafana/e2e'; e2e.scenario({ describeName: 'Dashboard time zone support', itName: 'Tests dashboard time zone scenarios', addScenarioDataSource: false, addScenarioDashBoard: false, skipScenario: true, scenario: () => { e2e.flows.openDashboard({ uid: '5SdHCasdf' }); const fromTimeZone = 'UTC'; const toTimeZone = 'America/Chicago'; const offset = offsetBetweenTimeZones(toTimeZone, fromTimeZone); const panelsToCheck = [ 'Random walk series', 'Millisecond res x-axis and tooltip', '2 yaxis and axis labels', 'Stacking value ontop of nulls', 'Null between points', 'Legend Table No Scroll Visible', ]; const timesInUtc: Record = {}; for (const title of panelsToCheck) { e2e.components.Panels.Panel.containerByTitle(title) .should('be.visible') .within(() => e2e.components.Panels.Visualization.Graph.xAxis .labels() .should('be.visible') .last() .should((element) => { timesInUtc[title] = element.text(); }) ); } e2e.components.PageToolbar.item('Dashboard settings').click(); e2e.components.TimeZonePicker.containerV2() .should('be.visible') .within(() => { e2e.components.Select.singleValue().should('have.text', 'Coordinated Universal Time'); e2e.components.Select.input().should('be.visible').click(); }); e2e.components.Select.option().should('be.visible').contains(toTimeZone).click(); // click to go back to the dashboard. e2e.components.BackButton.backArrow().click({ force: true }); e2e.components.RefreshPicker.runButtonV2().should('be.visible').click(); for (const title of panelsToCheck) { e2e.components.Panels.Panel.containerByTitle(title) .should('be.visible') .within(() => e2e.components.Panels.Visualization.Graph.xAxis .labels() .should('be.visible') .last() .should((element) => { const inUtc = timesInUtc[title]; const inTz = element.text(); const isCorrect = isTimeCorrect(inUtc, inTz, offset); expect(isCorrect, `Expect the panel "${title}" to have the new timezone applied but isn't`).to.be.equal( true ); }) ); } }, }); const isTimeCorrect = (inUtc: string, inTz: string, offset: number): boolean => { if (inUtc === inTz) { // we need to catch issues when timezone isn't changed for some reason like https://github.com/grafana/grafana/issues/35504 return false; } const reference = format(new Date(), 'yyyy-LL-dd'); const utcDate = toDate(parseISO(`${reference} ${inUtc}`)); const utcDateWithOffset = addHours(toDate(parseISO(`${reference} ${inUtc}`)), offset); const dayDifference = differenceInCalendarDays(utcDate, utcDateWithOffset); // if the utcDate +/- offset is the day before/after then we need to adjust reference const dayOffset = isBefore(utcDateWithOffset, utcDate) ? dayDifference * -1 : dayDifference; const tzDate = addDays(toDate(parseISO(`${reference} ${inTz}`)), dayOffset); // adjust tzDate with any dayOffset const diff = Math.abs(differenceInMinutes(utcDate, tzDate)); // use Math.abs if tzDate is in future return diff <= Math.abs(offset * 60); }; const offsetBetweenTimeZones = (timeZone1: string, timeZone2: string, when: Date = new Date()): number => { const t1 = convertDateToAnotherTimeZone(when, timeZone1); const t2 = convertDateToAnotherTimeZone(when, timeZone2); return (t1.getTime() - t2.getTime()) / (1000 * 60 * 60); }; const convertDateToAnotherTimeZone = (date: Date, timeZone: string): Date => { const dateString = date.toLocaleString('en-US', { timeZone: timeZone, }); return new Date(dateString); };